JS进阶 | 事件循环 — 宏任务与微任务

150 阅读2分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第6天,点击查看活动详情

在学习事件循环之前,需要了解一些JS异步的知识,前面有介绍:juejin.cn/post/710355…

因为js是单线程的,所以为了解决单线程运行阻塞的问题,使用异步的方式编程。 而js的异步任务又分为:宏任务和微任务,本篇我们来详细了解一下。

1、分类

  • JS任务
    • 同步任务
    • 异步任务
      • 宏任务(macrotask)
        • 整体代码
        • setTimeOut
        • setInterval
        • Ajax
        • I/O操作
      • 微任务(microtask)
        • Promise.then、Promise.catch、Promise.finally
        • process.nextTick
        • MutationObserve(前端回溯)

2、为什么引入微任务?

队列具有先进先出的性质,当有时需要处理高优先级的任务,就需要执行微任务。

当一个宏任务执行完毕,会进入任务队列将里面的微任务执行完,再进入下一个宏任务。

3、执行顺序:

  1. 在执行的时候,先执行同步任务(主线程执行栈中的代码)
  2. 遇到异步任务,放入相应的任务队列中,宏任务放入宏任务队列,微任务放入微任务队列
  3. 同步代码执行完毕后,将异步的微任务从队列中的代码调入主线程执行
  4. 微任务执行完毕后,将异步宏任务从队列中的代码调入主线程执行
  5. EventLoop(事件循环)直到结束

关于Node中的执行顺序:version 10 之后和浏览器的行为相同

4、面试常见题及分析

题目:

async function F1(){
  console.log('F1 Start');
  await F2();
  console.log('F1 End');
}
async function F2(){
  console.log('F2');
}
console.log('Script Start');
setTimeout(function() {
  console.log('setTimeout');
},1000)
F1();
new Promise(function(resolve) {
  console.log('Promise1');
  resolve()
}).then(function() {
  console.log('Promise2');
})
console.log('Script End');

解析:

  1. 输出'Script Start'

  2. setTimeout是宏任务,当前执行的是整体代码,属于宏任务,所以setTimeout放入宏任务队列下次执行

  3. 执行F1()

    1. 输出'F1 Start'
    2. 执行await F2(),这里相当于放入new Promise,后面的所有语句放入.then()执行。
       所以先执行F2(),输出'F2',.then()中的内容属于微任务,放入微任务队列。
        new Promise(function(resolve) {
           F2();
         }).then(function() {
           console.log('F1 End');
         })
    
  4. 执行Promise,输出'Promise1',.then()中的内容放入微任务队列。

  5. 输出'Script End'

  6. 此时宏任务队列中有setTimeout,微任务队列有 输出'F1 End'语句、输出'Promise2'语句

  7. 先执行微任务,先进先出的顺序。输出'F1 End' 、'Promise2'

  8. 执行宏任务setTimeout,输出'setTimeout' 最终的输出顺序是:

  • Script Start
  • F1 Start
  • F2
  • Promise1
  • Script End
  • F1 End
  • Promise2
  • setTimeout