从宏/微任务面试题理解event-loop

262 阅读2分钟

task/microtask执行顺序

先来一道面试题热身
问,执行顺序是什么?

Promise.resolve().then(function promise1() {
  console.log('promise1');
})

setTimeout(function setTimeout1(){
  console.log('setTimeout1')
  Promise.resolve().then(function promise2() {
    console.log('promise2');
  })
}, 0)

setTimeout(function setTimeout2(){
  console.log('setTimeout2')
}, 0)

分析一下运行过程:

  • 循环1: [task队列: script; microtask队列: []]
  1. 从task队列中取出script任务,推入栈中执行。
  2. promise1列为microtask,setTimeout1列为task,setTimeout2列为task。变为: [task队列: [setTimeout1, setTimeout2]; microtask队列: [promise1] ]
  3. script任务执行完毕,执行microtask checkpoint,取出microtask队列的promise1执行。此时微任务清空了
  • 循环2: [task队列: [setTimeout1, setTimeout2]; microtask队列: []]
  1. 从task队列中取出setTimeout1(因为task是队列遵循先进先出),推入栈中执行,将promise2列为microtask。 [task队列: [setTimeout2]; microtask队列: [ promise2 ]]
  2. 执行microtask checkpoint,取出microtask队列的promise2执行, 此时microtask 清空。
  • 循环3: [task队列: [setTimeout2]; microtask队列: [ ]]
  1. 从task队列中取出setTimeout2,推入栈中执行。
  2. setTimeout2任务执行完毕,执行microtask checkpoint。 [task队列: []; microtask队列: []] 所以,正确答案是
promise1
setTimeout1
promise2
setTimeout2

刚开始可能还会有一点困惑,困惑的点是基本上是同时添加了 宏、微任务,为什么微任务会在宏任务之前执行?

下面还有更详细的解释
setTimeout它只是一个task任务源,并不会立即执行,它只是将一个setTimeout任务插进task队列,得排到它,它里面的函数才会执行。Promise.then是microtask任务源,会将任务插进microtask队列。

Promise.resolve().then(function promise1() {
   console.log('promise1');
})

setTimeout(function setTimeout1(){
   console.log('setTimeout1')
}, 0)

以上面的例子来说,这两个仅仅是将相应的任务插进他们各自的队列中,此次event loop执行的task并不是setTimeout里的任务,setTimeout的任务排在下次循环了,当前只是插入task这个动作,还没轮到它执行。microtask队列的任务是会在当轮清空的,所以会看到promise1先于setTimeout1执行。并且上面两段代码顺序颠倒并不会影响结果。

关键点:每次执行一次宏任务结束后,会执行完所有的微任务并清空

for (const macroTask of macroTaskQueue) {
  handleMacroTask();
  
  for (const microTask of microTaskQueue) {
    handleMicroTask(microTask);
  }
}

js 的事件循环

知道了宏/微任务,再回到主题event-loop

  • 宏任务
    • 所有script代码
    • setTimeout
    • setInterval
    • setImmediate(浏览器暂时不支持,只有IE10支持,具体可见MDN)
    • I/O
    • UI Rendering。
  • 微任务
    • Process.nextTick(Node独有)
    • Promise
    • Object.observe(废弃)
    • MutationObserver

最主要是这个图,其他的还是看原文吧,参考文章第二篇讲的很透彻

参考文章:
从event loop规范探究javaScript异步及浏览器更新渲染时机
【THE LAST TIME】彻底吃透 JavaScript 执行机制