-
事件循环机制
- 同步与异步
- 微任务与宏任务(异步事件)
- 任务执行顺序
- 最终总结
事件循环机制
同步与异步
我们先思考两个问题,如下:
为什么会存在同步和异步的概念?
我们的JavaScript是单线程的,也就是我们的工作流水线的只有一条。如果我们的任务全放在流水线上,其中一个任务出现问题就会阻塞后面的任务,导致我们的工作流水线卡住。因此为了更加高效合理利用这条流水线,在JavaScript中出现了同步与异步的概念。
同步与异步任务如何在一条流水线上工作?
同样是一条流水线。我们的有不同的产品,有的产品能瞬间完成,有的产品需要耗费大量时间才能完成。那么我们给这堆产品分为两类,能瞬间完成的产品先放入流水线前面,而需要耗费大量时间的产品则放在流水线后面。将所有的产品按顺序从流水线上执行。即使后面有部分产品制作很慢,但我们流水线前面的大部分产品都能顺利完成。也就是说,我们能够及时交付大部分的产品,让老板基本满意就足够了。后面的产品我们再慢慢做也不迟。
同步
同步任务:我们的任务不会造成流水线阻塞,瞬间就能完成。那我们直接把这些任务定义为同步任务。
同步事件:new Promise()、async关键字、console对象方法
异步
异步任务:我们的任务可能会造成流水线阻塞,需要时间才能完成。那我们直接把这些任务定义为异步任务。
异步事件:我们先不介绍异步事件,因为异步事件又分为微任务与宏任务。我们下面再介绍。
微任务与宏任务(异步事件)
微任务事件:Promise.then()、await 后面的语句、queueMiscrotask、MutationObserver、process.nextTick( node.js环境 )[ 微任务中最先执行 ]
宏任务事件:setTimout() 和 setInterval()、<script>脚本、I/O、UI交互事件、setImmediate( node.js环境 )[ 宏任务中最后执行 ]
注意:异步事件分为微任务和宏任务,其中微任务优先于宏任务执行。
任务执行顺序
执行栈:同步代码会首先归类到此处待执行。按照代码的书写顺序(上到下)入栈。
微任务队列:异步中的微任务代码归类到此处。按照代码的书写顺序(上到下)入队。当执行栈为空时,微任务会出队进入执行栈。
宏任务队列:异步中的宏任务代码归类到此处。按照代码的书写顺序(上到下)入队。当微任务队列为空时,宏任务会出队进入执行栈。
我们采用验证法做几道题
第一道如下:
console.log(1)
setTimeout(() => {
console.log(2)
setTimeout(() => {
console.log(3)
}, 1000)
}, 1000)
new Promise((resolve, reject) => {
console.log(4)
resolve(5) // 之后会调用 then 方法
console.log(6)
}).then((resolve, reject) => {
console.log(resolve)
})
function add(a, b) {
return a + b
}
async function foo() {
console.log(7)
let num = await add(4, 4)
console.log(9)
return num
}
foo().then((num) => {
console.log(num)
})
console.log(10) // 1 4 6 7 10 5 9 8 2 3
以上代码没有出现宏任务嵌套微任务,微任务嵌套宏任务的情况。那么我们可以按编号(对应的输出语句)直接对执行栈、微任务和宏任务分类如下:
第一次分析执行栈(同步任务):1 4 6 7 10
第二次分析微任务:5 9 8
第三次分析宏任务:2 3
然后我们按执行顺序将答案组合在一起。最终答案:1 4 6 7 10 5 9 8 2 3。
注意:以上分析方法只能应对异步函数无嵌套的情况。
接下来我们增加难度,宏任务嵌套微任务,微任务嵌套宏任务。第二题如下:
console.log(1)
setTimeout(() => {
console.log(2)
new Promise((resolve, reject) => {
console.log(3)
resolve(4)
}).then((resolve, reject) => {
console.log(resolve)
})
console.log(5)
}, 1000)
console.log(6)
new Promise((resolve, reject) => {
console.log(7)
resolve(8)
setTimeout(() => {
console.log(9)
}, 1000)
}).then((resolve, reject) => {
console.log(resolve)
})
console.log(10) // 1 6 7 10 8 2 3 5 4 9
以上代码出现了宏任务嵌套微任务,微任务嵌套宏任务的情况。
提示:外部(全局作用域)的代码可以按任务执行顺序分析,异步函数内部其实也可以按照我们的任务执行顺序分析。其分析过程是一样的。
最终答案:1 6 7 10 8 2 3 5 4 9。还有很多特例,可自行编写异步函数测试。总结方法
最终总结
分析方法:先对代码进行事件分类,然后分别放入对应类型的三个地方(执行栈、微任务队列和宏任务队列)再按照我们的执行顺序,发生异步函数嵌套情况时,也可以对其内部应用这套规则解决。
任务执行顺序如下:
- 同步(new Promise()、async关键字、console对象方法)
- process.nextTick(node.js环境)
- 微任务(异步)(Promise().then()、await 后面的语句)
- 宏任务(异步)(定时器函数、<script>脚本、I/O、UI交互事件)
- setImmediate(node.js环境)(当前事件循环结束后执行)
参考
【前端八股文】事件循环-宏任务和微任务