最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • 【高级前端】难以理解的Event loop

    正文概述 掘金(杜镇恶)   2021-06-13   483

    event loop 较难理解,面试中经常出现考察event loop执行顺序的问题,本文详细讲解event loop。

    Event loop定义

    1.这是 w3c 的定义

    2.这是mdn的定义

    3.这是node.js的定义

    例题1:

    // 回答输出顺序
    setTimeout(() => {
    	console.log('setTimeout异步')
    }, 0)
    console.log('同步')
    

    答案:

    同步
    setTimeout异步
    

    回答错误的同学建议先复习一下异步JavaScript

    例题2:

    // 回答输出顺序
    setTimeout(() => {
    	console.log('setTimeout异步')
    }, 0)
    Promise.resolve().then(() =>{
        console.log('promise异步')
    })
    console.log('同步')
    

    答案:

    同步
    promise异步
    setTimeout异步
    

    代码promise异步任务代码写在setTimeout异步任务代码之后,执行结果却是promise异步任务先执行setTimeout异步任务后执行,这是由event loop控制的,event loop控制异步任务回调的执行顺序。

    task、microtask定义

    event loop把异步任务分为task(:也有说法是macrotask,不过我没有在w3c和mdn中找到macrotask的说法和定义,下文统一用task)和microtask.

    task

    大多数异步操作都是task,例如:Event事件(click,mousemove等监听事件),fetch(网络请求),Parsing(浏览器对html的解析),Dom操作(dom的增删等),定时器(setTimeout/setInterval)等(:记住microtask就可以,除了microtask就是task,microtask好记)。

    浏览器执行代码的时候遇到task会将task推入到一个task queue队列中,等同步代码都执行完,浏览器会在合适的时机按顺序依次执行task quque内的内容,直到清空task queue队列。
    task queue队列执行中及清空后,新产生的task推入一个新的task queue中,等待浏览器的下次执行(:这段不是很严谨,不过代码的执行顺序是高度等效的,严谨的解释请参考w3c)。

    // 伪代码近似表示
    const taskQueue = [] // task queue队列
    // 遇到task任务就加到队列里
    taskQueue.push(() => {console.log('task任务1')})
    taskQueue.push(() => {console.log('task任务2')})
    taskQueue.push(() => {console.log('task任务3')})
    while(taskQueue.length) {
      // 按顺序一个一个执行
    	 taskQueue.shift()()     
    }
    

    例题:

    // 回答输出顺序
    console.log('同步1') // 同步任务直接执行
    setTimeout(() => {
    	console.log('setTimeout异步1') // 丢到 task queue里的第1个task
    }, 0)
    setTimeout(() => {
    	console.log('setTimeout异步2') // 丢到 task queue里的第2个task
    }, 0)
    setTimeout(() => {
    	console.log('setTimeout异步3') // 丢到 task queue里的第3个task
    }, 0)
    console.log('同步2') // 同步任务直接执行
    // 现在执行task queue!
    

    答案:

    // 先执行同步任务
    同步1
    同步2
    // 后依次执行task queue任务
    setTimeout异步1
    setTimeout异步2
    setTimeout异步3
    

    microtask

    w3c:

    mdn:

    单纯的microtask执行在表现上和task区别不大,microtask 任务推入microtask queue,执行完同步代码后,在依次执行microtask queue内的回调,但是microtask queue 执行过程中产生的新microtask 会直接推入当前microtask queue最后被执行,不需要像task queue一样等待浏览器下次执行。

    例题:

    Promise.resolve().then(() => {
        console.log('promise1')
    })
    Promise.resolve().then(() => {
        console.log('promise2')
    }).then(() => {
        console.log('promise4')
    })
    Promise.resolve().then(() => {
        console.log('promise3')
    })
    console.log('同步1')
    

    答案:

    同步1
    promise1
    promise2
    promise3
    promise4
    

    解析:

    // 初始microtask queue 为空[]
    /* 
    微任务:() => {
        console.log('promise1')
    }入队,队列[
        () => {
            console.log('promise1')
        }
    ] */
    Promise.resolve().then(() => {
        console.log('promise1')
    })
    /* 
    微任务:(() => {
        console.log('promise2')
    }).then(() => {
        console.log('promise4')
    })入队,队列[
        () => {
            console.log('promise1')
        },
        (() => {
            console.log('promise2')
        }).then(() => {
            console.log('promise4')
        })
    ] */
    Promise.resolve().then(() => {
        console.log('promise2')
    }).then(() => {
        console.log('promise4')
    })
    /* 
    微任务:() => {
        console.log('promise3')
    }入队,队列[
        () => {
            console.log('promise1')
        },
        (() => {
            console.log('promise2')
        }).then(() => {
            console.log('promise4')
        }),
        () => {
             console.log('promise3')
        }
    ] */
    Promise.resolve().then(() => {
        console.log('promise3')
    })
    // 执行 console.log('同步1')
    console.log('同步1')
    // 执行 microtask queue
    /* 
    执行 console.log('promise1'),队列 [
        (() => {
            console.log('promise2')
        }).then(() => {
            console.log('promise4')
        }),
        () => {
             console.log('promise3')
        }
    ] */
    
    /* 
    执行 console.log('promise2'),
    微任务:() => {
         console.log('promise4')
    }入队,队列 [
        () => {
             cnsole.log('promise3')
        },
        () => {
            console.log('promise4')
        }
    ] */
    
    /* 
    执行 console.log('promise3'),队列 [
        () => {
            console.log('promise4')
        }
    ] */
    
    /* 
    执行 console.log('promise4'),队列 [], 微任务执行完毕 */
    
    

    task vs micotask

    mdn:

    看起来云里雾里,只需要记住一句:在task queue中每个task执行之前,都会先执行microtask queue 中的microtask,因为task的执行,可能会向microtask queue中添加新的microtask.
    例题:

    setTimeout(() => {
        console.log('setTimeout1')
    }, 0)
    
    setTimeout(() => {
        console.log('setTimeout2')
        Promise.resolve().then(() => {
            console.log('promise2')
        })
    }, 0)
    
    setTimeout(() => {
        console.log('setTimeou3')
    }, 0)
    
    Promise.resolve().then(() => {
        console.log('promise1')
    })
    console.log('同步1')
    

    答案:

    同步1
    promise1
    setTimeout1
    setTimeout2
    promise2
    setTimeou3
    

    解析:

    // 初始宏任务队列 []
    // 初始微任务队列 []
    /* 
    宏任务:() => {
        console.log('setTimeout1')
    }入队宏任务队列,此时
    宏任务队列:[
    () => {
        console.log('setTimeout1')
    }]
    微任务队列: []
    
    */
    setTimeout(() => {
        console.log('setTimeout1')
    }, 0)
    /* 
    宏任务:() => {
        console.log('setTimeout2')
        Promise.resolve().then(() => {
            console.log('promise2')
        })
    }入队宏任务队列,此时
    宏任务队列:[
    () => {
        console.log('setTimeout1')
    },
    () => {
        console.log('setTimeout2')
        Promise.resolve().then(() => {
            console.log('promise2')
        })
    }]
    微任务队列: []
    
    */
    setTimeout(() => {
        console.log('setTimeout2')
        Promise.resolve().then(() => {
            console.log('promise2')
        })
    }, 0)
    /* 
    宏任务:() => {
        console.log('setTimeout3')
    }入队宏任务队列,此时
    宏任务队列:[
    () => {
        console.log('setTimeout1')
    },
    () => {
        console.log('setTimeout2')
        Promise.resolve().then(() => {
            console.log('promise2')
        })
    },
    () => {
        console.log('setTimeout3')
    }]
    微任务队列: []
    
    */
    setTimeout(() => {
        console.log('setTimeou3')
    }, 0)
    /* 
    微任务:() => {
        console.log('promise1')
    }入队宏任务队列,此时
    宏任务队列:[
    () => {
        console.log('setTimeout1')
    },
    () => {
        console.log('setTimeout2')
        Promise.resolve().then(() => {
            console.log('promise2')
        })
    },
    () => {
        console.log('setTimeout3')
    }]
    微任务队列: [
        () => {
        console.log('promise1')
    }
    ]
    
    */
    Promise.resolve().then(() => {
        console.log('promise1')
    })
    // 执行同步代码 console.log('同步1')
    console.log('同步1')
    /* 
    此时队列情况:
    宏任务队列:[
    () => {
        console.log('setTimeout1')
    },
    () => {
        console.log('setTimeout2')
        Promise.resolve().then(() => {
            console.log('promise2')
        })
    },
    () => {
        console.log('setTimeout3')
    }]
    微任务队列: [
        () => {
        console.log('promise1')
    }
    ]
    微任务优先级更高,执行宏任务前先执行微任务队列
    执行微任务:() => {
        console.log('promise1')
    }
    微任务队列: []
    宏任务队列:[
    () => {
        console.log('setTimeout1')
    },
    () => {
        console.log('setTimeout2')
        Promise.resolve().then(() => {
            console.log('promise2')
        })
    },
    () => {
        console.log('setTimeout3')
    }]
    
    */
    
    /* 
    微任务队列执行完毕,开始执行宏任务
    执行宏任务:() => {
        console.log('setTimeout1')
    }
    微任务队列: []
    宏任务队列:[
    () => {
        console.log('setTimeout2')
        Promise.resolve().then(() => {
            console.log('promise2')
        })
    },
    () => {
        console.log('setTimeout3')
    }]
    */
    
    /* 
    微任务优先级更高,执行宏任务前先执行微任务队列
    微任务队列执行完毕(为空),开始执行宏任务
    执行宏任务:() => {
        console.log('setTimeout2')
        // 这里产生了微任务,() => {
        //    console.log('promise2')
        //}入队微任务队列
        Promise.resolve().then(() => {
            console.log('promise2')
        })
    },
    微任务队列: [
        () => {
            console.log('promise2')
        }
    ]
    宏任务队列:[
    () => {
        console.log('setTimeout3')
    }]
    */
    
    /* 
    微任务优先级更高,执行宏任务前先执行微任务队列
    执行微任务:() => {
            console.log('promise2')
        }
    微任务队列: []
    宏任务队列:[
    () => {
        console.log('setTimeout3')
    }]
    */
    
    /* 
    微任务队列执行完毕,开始执行宏任务
    执行宏任务:() => {
        console.log('setTimeout3')
    },
    微任务队列: []
    宏任务队列:[]
     */
    // 全部执行完成
    

    微任务和宏任务的执行都用可能产生新的微任务和宏任务,因此先分别画出微任务和宏任务队列,然后根据执行入队和出队,否则很容易出错。

    习题

    promise习题

    promise要注意区分哪些是同步代码,哪些是异步代码
    例题:

    new Promise((resolve) => {
        console.log('这是promise的同步1')
        resolve()
        console.log('这是promise的同步2')
    }).then(() =>{
        console.log('这是promise的异步1')
    })
    console.log('同步')
    

    答案:

    这是promise的同步1
    这是promise的同步2
    同步
    这是promise的异步1
    

    解析:

    // 不要看到promise就当异步处理,then里面才是异步,new Promise 声明是同步!
    new Promise((resolve) => {
        // 这里是同步!
        console.log('这是promise的同步1')
        // 这里相当于then才是异步
        resolve()
        // 这里是同步!
        console.log('这是promise的同步2')
    }).then(() =>{
        console.log('这是promise的异步1')
    })
    // 同步,但是在promise声明的后面!
    console.log('同步')
    

    async习题

    async函数中的await左下部分相当于new Promise().then

    /* 
    async () => {
        xxx代码
        这上面也是右上
    ----------------|
    这里开始是左下  | 这里是右上
        const p =  | await xxx();
                   |-------------------
            这里也是左下       
               xxx代码    
    }
    */
    


    例题1:

    const asyncFunction = async () => {
    
        console.log('async同步1')
    
        await new Promise((resolve) => {
            console.log('这是promise的同步1')
            resolve()
            console.log('这是promise的同步2')
        })
    
        console.log('async异步1')
    }
    
    asyncFunction()
    
    console.log('同步1')
    

    答案:

    async同步1
    这是promise的同步1
    这是promise的同步2
    同步1
    async异步1
    

    解析:

    // async函数 第一个await 前面是同步,之后的相当于new Promise.then()是微任务
    const asyncFunction =  () => {
        console.log('async同步1')
    
         new Promise((resolve) => {
            console.log('这是promise的同步1')
            resolve()
            console.log('这是promise的同步2')
        }).then(() => {
            console.log('async异步1')
        })
    }
    
    asyncFunction()
    
    console.log('同步1')
    

    例题2:

    const asyncFunction = async () => {
        console.log('async同步1')
        await new Promise((resolve) => {
            console.log('这是promise的同步1')
            resolve()
            console.log('这是promise的同步2')
        })
        console.log('async异步1')
        await new Promise((resolve) => {
            resolve()
        })
        console.log('async异步2')
    }
    
    Promise.resolve().then(() => {
        console.log('promise异步1')
    })
    asyncFunction()
    Promise.resolve().then(() => {
        console.log('promise异步2')
    })
    
    console.log('同步1')
    

    答案:

    async同步1
    这是promise的同步1
    这是promise的同步2
    同步1
    promise异步1
    async异步1
    promise异步2
    async异步2
    

    解析:

    // 只要把async函数内的await都换成promise就可以了
    const asyncFunction = () => {
        console.log('async同步1')
        new Promise((resolve) => {
            console.log('这是promise的同步1')
            resolve()
            console.log('这是promise的同步2')
            // 第一个await 变成.then
        }).then(() => {
            console.log('async异步1')
            return new Promise((resolve) => {
                resolve()
            })
            // 第二个await也变成.then
        }).then(() => {
            console.log('async异步2')
        })
    
    }
    
    Promise.resolve().then(() => {
        console.log('promise异步1')
    })
    asyncFunction()
    Promise.resolve().then(() => {
        console.log('promise异步2')
    })
    
    console.log('同步1')
    

    综合习题

    async function async1(){
        console.log('async1 start')
        await async2()
        console.log('async1 end')
    }
    async function async2(){
        console.log('async2')
    }
    console.log('script start')
    setTimeout(function(){
        console.log('setTimeout') 
    },0)  
    async1();
    new Promise(function(resolve){
        console.log('promise1')
        resolve();
    }).then(function(){
        console.log('promise2')
    })
    console.log('script end')
    

    答案:

    script start
    async1 start
    async2
    promise1
    script end
    async1 end
    promise2
    setTimeout
    

    解析:

    // 1.先把async函数都改写成promise形式
    function async1(){
        // 同步
        console.log('async1 start')
        new Promise((resolve) => {
            // resolve中的函数同步执行
            resolve(async2())
            // then入队微任务
        }).then(() => {
            console.log('async1 end')
        })
    }
    function async2(){
        // 同步
        console.log('async2')
    }
    console.log('script start')
    // 入队宏任务
    setTimeout(function(){
        console.log('setTimeout') 
    },0)  
    async1();
    new Promise(function(resolve){
        // 同步
        console.log('promise1')
        resolve();
        // 入队微任务
    }).then(function(){
        console.log('promise2')
    })
    console.log('script end')
    

    总结

    event loop 问题尤其是涉及到async的情况下非常复杂,因为篇幅限制,本文无法列举全部内容,需要读者多尝试总结,另外附上本文没有说明的event loop其他情况,读者查看对应连接即可:
    1.为什么需要微任务
    2.node环境下的setImmediate、process.nextTick


    起源地下载网 » 【高级前端】难以理解的Event loop

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元