目录

  • 事件循环机制
    • 同步与异步
    • 微任务与宏任务(异步事件)
    • 任务执行顺序
    • 最终总结

事件循环机制同步与异步

我们先思考两个问题,如下:

为什么会存在同步和异步的概念?

我们的JavaScript是单线程的,也就是我们的工作流水线的只有一条。如果我们的任务全放在流水线上,其中一个任务出现问题就会阻塞后面的任务,导致我们的工作流水线卡住。因此为了更加高效合理利用这条流水线,在JavaScript中出现了同步与异步的概念。

同步与异步任务如何在一条流水线上工作?

同样是一条流水线。我们的有不同的产品,有的产品能瞬间完成,有的产品需要耗费大量时间才能完成。那么我们给这堆产品分为两类,能瞬间完成的产品先放入流水线前面,而需要耗费大量时间的产品则放在流水线后面。将所有的产品按顺序从流水线上执行。即使后面有部分产品制作很慢,但我们流水线前面的大部分产品都能顺利完成。也就是说,我们能够及时交付大部分的产品,让老板基本满意就足够了。后面的产品我们再慢慢做也不迟。

同步

同步任务:我们的任务不会造成流水线阻塞,瞬间就能完成。那我们直接把这些任务定义为同步任务。

同步事件:new Promise()、async关键字、console对象方法

异步

异步任务:我们的任务可能会造成流水线阻塞,需要时间才能完成。那我们直接把这些任务定义为异步任务。

异步事件:我们先不介绍异步事件,因为异步事件又分为微任务与宏任务。我们下面再介绍。

微任务与宏任务(异步事件)

微任务事件:Promise.then()、await 后面的语句、queueMiscrotask、MutationObserver、process.nextTick( node.js环境 )[ 微任务中最先执行 ]

宏任务事件:setTimout() 和 setInterval()、脚本、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。还有很多特例,可自行编写异步函数测试。总结方法

最终总结

分析方法:先对代码进行事件分类,然后分别放入对应类型的三个地方(执行栈、微任务队列和宏任务队列)再按照我们的执行顺序,发生异步函数嵌套情况时,也可以对其内部应用这套规则解决。

任务执行顺序如下

  1. 同步(new Promise()、async关键字、console对象方法)
  2. process.nextTick(node.js环境)
  3. 微任务(异步)(Promise().then()、await 后面的语句)
  4. 宏任务(异步)(定时器函数、脚本、I/O、UI交互事件)
  5. setImmediate(node.js环境)(当前事件循环结束后执行)

参考

【前端八股文】事件循环-宏任务和微任务