下面来看一道比较典型的问题,通过这个问题来对比几种异步编程方法:红灯 3s 亮一次,绿灯 1s 亮一次,黄灯 2s 亮一次;如何让三个灯不断交替重复亮灯?
三个亮灯函数:
- function red() {
- console.log('red');
- }
- function green() {
- console.log('green');
- }
- function yellow() {
- console.log('yellow');
- }
-
这道题复杂的地方在于需要“交替重复”亮灯,而不是“亮完一次”就结束了。
- const task = (timer, light, callback) => {
- setTimeout(() => {
- if (light === 'red') {
- red()
- }
- else if (light === 'green') {
- green()
- }
- else if (light === 'yellow') {
- yellow()
- }
- callback()
- }, timer)
- }
- task(3000, 'red', () => {
- task(2000, 'green', () => {
- task(1000, 'yellow', Function.prototype)
- })
- })
-
这里存在一个 bug:代码只是完成了一次流程,执行后红黄绿灯分别只亮一次。该如何让它交替重复进行呢?
上面提到过递归,可以递归亮灯的一个周期:
- const step = () => {
- task(3000, 'red', () => {
- task(2000, 'green', () => {
- task(1000, 'yellow', step)
- })
- })
- }
- step()
-
注意看黄灯亮的回调里又再次调用了 step 方法 以完成循环亮灯。
- const task = (timer, light) =>
- new Promise((resolve, reject) => {
- setTimeout(() => {
- if (light === 'red') {
- red()
- }
- else if (light === 'green') {
- green()
- }
- else if (light === 'yellow') {
- yellow()
- }
- resolve()
- }, timer)
- })
- const step = () => {
- task(3000, 'red')
- .then(() => task(2000, 'green'))
- .then(() => task(2100, 'yellow'))
- .then(step)
- }
- step()
-
这里将回调移除,在一次亮灯结束后,resolve 当前 promise,并依然使用递归进行。
- const taskRunner = async () => {
- await task(3000, 'red')
- await task(2000, 'green')
- await task(2100, 'yellow')
- taskRunner()
- }
- taskRunner()
-
- // 使用闭包实现
- for (var i = 0; i < 5; i++) {
- (function(i) {
- setTimeout(function() {
- console.log(i);
- }, i * 1000);
- })(i);
- }
- // 使用 let 块级作用域
- for (let i = 0; i < 5; i++) {
- setTimeout(function() {
- console.log(i);
- }, i * 1000);
- }
-
有30个小孩儿,编号从1-30,围成一圈依此报数,1、2、3 数到 3 的小孩儿退出这个圈, 然后下一个小孩 重新报数 1、2、3,问最后剩下的那个小孩儿的编号是多少?
- function childNum(num, count){
- let allplayer = [];
- for(let i = 0; i < num; i++){
- allplayer[i] = i + 1;
- }
-
- let exitCount = 0; // 离开人数
- let counter = 0; // 记录报数
- let curIndex = 0; // 当前下标
-
- while(exitCount < num - 1){
- if(allplayer[curIndex] !== 0) counter++;
-
- if(counter == count){
- allplayer[curIndex] = 0;
- counter = 0;
- exitCount++;
- }
- curIndex++;
- if(curIndex == num){
- curIndex = 0
- };
- }
- for(i = 0; i < num; i++){
- if(allplayer[i] !== 0){
- return allplayer[i]
- }
- }
- }
- childNum(30, 3)
-
- let imageAsync=(url)=>{
- return new Promise((resolve,reject)=>{
- let img = new Image();
- img.src = url;
- img.οnlοad=()=>{
- console.log(`图片请求成功,此处进行通用操作`);
- resolve(image);
- }
- img.οnerrοr=(err)=>{
- console.log(`失败,此处进行失败的通用操作`);
- reject(err);
- }
- })
- }
-
- imageAsync("url").then(()=>{
- console.log("加载成功");
- }).catch((error)=>{
- console.log("加载失败");
- })
-
- class EventCenter{
- // 1. 定义事件容器,用来装事件数组
- let handlers = {}
-
- // 2. 添加事件方法,参数:事件名 事件方法
- addEventListener(type, handler) {
- // 创建新数组容器
- if (!this.handlers[type]) {
- this.handlers[type] = []
- }
- // 存入事件
- this.handlers[type].push(handler)
- }
-
- // 3. 触发事件,参数:事件名 事件参数
- dispatchEvent(type, params) {
- // 若没有注册该事件则抛出错误
- if (!this.handlers[type]) {
- return new Error('该事件未注册')
- }
- // 触发事件
- this.handlers[type].forEach(handler => {
- handler(...params)
- })
- }
-
- // 4. 事件移除,参数:事件名 要删除事件,若无第二个参数则删除该事件的订阅和发布
-
- removeEventListener(type, handler) {
- if (!this.handlers[type]) {
- return new Error('事件无效')
- }
- if (!handler) {
- // 移除事件
- delete this.handlers[type]
- } else {
- const index = this.handlers[type].findIndex(el => el === handler)
- if (index === -1) {
- return new Error('无该绑定事件')
- }
- // 移除事件
- this.handlers[type].splice(index, 1)
- if (this.handlers[type].length === 0) {
- delete this.handlers[type]
- }
- }
- }
- }
-
- function findMostWord(article) {
- // 合法性判断
- if (!article) return;
- // 参数处理
- article = article.trim().toLowerCase();
- let wordList = article.match(/[a-z]+/g),
- visited = [],
- maxNum = 0,
- maxWord = "";
- article = " " + wordList.join(" ") + " ";
- // 遍历判断单词出现次数
- wordList.forEach(function(item) {
- if (visited.indexOf(item) < 0) {
- // 加入 visited
- visited.push(item);
- let word = new RegExp(" " + item + " ", "g"),
- num = article.match(word).length;
- if (num > maxNum) {
- maxNum = num;
- maxWord = item;
- }
- }
- });
- return maxWord + " " + maxNum;
- }
-
- (async () => {
- class HttpRequestUtil {
- async get(url) {
- const res = await fetch(url);
- const data = await res.json();
- return data;
- }
- async post(url, data) {
- const res = await fetch(url, {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json'
- },
- body: JSON.stringify(data)
- });
- const result = await res.json();
- return result;
- }
- async put(url, data) {
- const res = await fetch(url, {
- method: 'PUT',
- headers: {
- 'Content-Type': 'application/json'
- },
- data: JSON.stringify(data)
- });
- const result = await res.json();
- return result;
- }
- async delete(url, data) {
- const res = await fetch(url, {
- method: 'DELETE',
- headers: {
- 'Content-Type': 'application/json'
- },
- data: JSON.stringify(data)
- });
- const result = await res.json();
- return result;
- }
- }
- const httpRequestUtil = new HttpRequestUtil();
- const res = await httpRequestUtil.get('http://golderbrother.cn/');
- console.log(res);
- })();
所谓的原型链继承就是让新实例的原型等于父类的实例:
- //父方法
- function SupperFunction(flag1){
- this.flag1 = flag1;
- }
-
- //子方法
- function SubFunction(flag2){
- this.flag2 = flag2;
- }
-
- //父实例
- var superInstance = new SupperFunction(true);
-
- //子继承父
- SubFunction.prototype = superInstance;
-
- //子实例
- var subInstance = new SubFunction(false);
- //子调用自己和父的属性
- subInstance.flag1; // true
- subInstance.flag2; // false
-
- let obj = {}
- let input = document.getElementById('input')
- let span = document.getElementById('span')
- // 数据劫持
- Object.defineProperty(obj, 'text', {
- configurable: true,
- enumerable: true,
- get() {
- console.log('获取数据了')
- },
- set(newVal) {
- console.log('数据更新了')
- input.value = newVal
- span.innerHTML = newVal
- }
- })
- // 输入监听
- input.addEventListener('keyup', function(e) {
- obj.text = e.target.value
- })
-
- // hash路由
- class Route{
- constructor(){
- // 路由存储对象
- this.routes = {}
- // 当前hash
- this.currentHash = ''
- // 绑定this,避免监听时this指向改变
- this.freshRoute = this.freshRoute.bind(this)
- // 监听
- window.addEventListener('load', this.freshRoute, false)
- window.addEventListener('hashchange', this.freshRoute, false)
- }
- // 存储
- storeRoute (path, cb) {
- this.routes[path] = cb || function () {}
- }
- // 更新
- freshRoute () {
- this.currentHash = location.hash.slice(1) || '/'
- this.routes[this.currentHash]()
- }
- }
-
- // 递归
- function fn (n){
- if(n==0) return 0
- if(n==1) return 1
- return fn(n-2)+fn(n-1)
- }
- // 优化
- function fibonacci2(n) {
- const arr = [1, 1, 2];
- const arrLen = arr.length;
-
- if (n <= arrLen) {
- return arr[n];
- }
-
- for (let i = arrLen; i < n; i++) {
- arr.push(arr[i - 1] + arr[ i - 2]);
- }
-
- return arr[arr.length - 1];
- }
- // 非递归
- function fn(n) {
- let pre1 = 1;
- let pre2 = 1;
- let current = 2;
-
- if (n <= 2) {
- return current;
- }
-
- for (let i = 2; i < n; i++) {
- pre1 = pre2;
- pre2 = current;
- current = pre1 + pre2;
- }
-
- return current;
- }
-
用一个滑动窗口装没有重复的字符,枚举字符记录最大值即可。用 map 维护字符的索引,遇到相同的字符,把左边界移动过去即可。挪动的过程中记录最大长度:
- var lengthOfLongestSubstring = function (s) {
- let map = new Map();
- let i = -1
- let res = 0
- let n = s.length
- for (let j = 0; j < n; j++) {
- if (map.has(s[j])) {
- i = Math.max(i, map.get(s[j]))
- }
- res = Math.max(res, j - i)
- map.set(s[j], j)
- }
- return res
- };
-
setInterval 的作用是每隔一段指定时间执行一个函数,但是这个执行不是真的到了时间立即执行,它真正的作用是每隔一段时间将事件加入事件队列中去,只有当当前的执行栈为空的时候,才能去从事件队列中取出事件执行。所以可能会出现这样的情况,就是当前执行栈执行的时间很长,导致事件队列里边积累多个定时器加入的事件,当执行栈结束的时候,这些事件会依次执行,因此就不能到间隔一段时间执行的效果。
针对 setInterval 的这个缺点,我们可以使用 setTimeout 递归调用来模拟 setInterval,这样我们就确保了只有一个事件结束了,我们才会触发下一个定时器事件,这样解决了 setInterval 的问题。
实现思路是使用递归函数,不断地去执行 setTimeout 从而达到 setInterval 的效果
- function mySetInterval(fn, timeout) {
- // 控制器,控制定时器是否继续执行
- var timer = {
- flag: true
- };
- // 设置递归函数,模拟定时器执行。
- function interval() {
- if (timer.flag) {
- fn();
- setTimeout(interval, timeout);
- }
- }
- // 启动定时器
- setTimeout(interval, timeout);
- // 返回控制器
- return timer;
- }
循环引用对象本来没有什么问题,但是序列化的时候就会发生问题,比如调用JSON.stringify()对该类对象进行序列化,就会报错: Converting circular structure to JSON.
下面方法可以用来判断一个对象中是否已存在循环引用:
- const isCycleObject = (obj,parent) => {
- const parentArr = parent || [obj];
- for(let i in obj) {
- if(typeof obj[i] === 'object') {
- let flag = false;
- parentArr.forEach((pObj) => {
- if(pObj === obj[i]){
- flag = true;
- }
- })
- if(flag) return true;
- flag = isCycleObject(obj[i],[...parentArr,obj[i]]);
- if(flag) return true;
- }
- }
- return false;
- }
-
-
- const a = 1;
- const b = {a};
- const c = {b};
- const o = {d:{a:3},c}
- o.c.b.aa = a;
-
- console.log(isCycleObject(o)
-
- 查找有序二维数组的目标值:
- var findNumberIn2DArray = function(matrix, target) {
- if (matrix == null || matrix.length == 0) {
- return false;
- }
- let row = 0;
- let column = matrix[0].length - 1;
- while (row < matrix.length && column >= 0) {
- if (matrix[row][column] == target) {
- return true;
- } else if (matrix[row][column] > target) {
- column--;
- } else {
- row++;
- }
- }
- return false;
- };
二维数组斜向打印:
- function printMatrix(arr){
- let m = arr.length, n = arr[0].length
- let res = []
-
- // 左上角,从0 到 n - 1 列进行打印
- for (let k = 0; k < n; k++) {
- for (let i = 0, j = k; i < m && j >= 0; i++, j--) {
- res.push(arr[i][j]);
- }
- }
-
- // 右下角,从1 到 n - 1 行进行打印
- for (let k = 1; k < m; k++) {
- for (let i = k, j = n - 1; i < m && j >= 0; i++, j--) {
- res.push(arr[i][j]);
- }
- }
- return res
- }