被误导的event loop:根本就没有宏任务

664 阅读3分钟

问起event loop,有很多人会提到一个关键词:“宏任务”。关于event loop的文章如果看多了,会发现不同作者对宏任务的定义不一样。有些认为宏任务就是同步任务,有些认为宏任务是区别于微任务的异步任务。 哪一种是对的呢?我查了各种资料,发现并没有关于宏任务的定义。

event loop 的定义

ECMAScript 语言规范中并没有提及到event loop,它的定义在HTML规范中。event loop的循环机制可以看这里event-loop-processing-model。不过首先要解释几个重要的概念

task

task封装了一套算法,负责dom操作,html解析,事件,fetch等操作。每个task包含了js的执行环境、工作步骤等。用更容易理解的方式讲,可以将一个script标签看成一个task。

task queue

一个任务队列是一组(set)任务,要注意虽然叫queue,但并不是以队列的形式组织任务。因为在event loop模型中,第一个可执行的task并不一定是队列中的第一个。

microtask和microtask queue

每个event loop有一个微任务队列。

微任务是微任务队列算法创建的任务,微任务队列算法中最重要的一条是异步,此外,微任务队列是queue,而不是set。虽然微任务也属于task,但微任务队列不是任务队列

微任务的执行时机

event-loop-processing-model中定义了微任务的执行时机:event loop先取出一个可执行的task,task的steps执行结束后执行微任务检查点,只要有微任务,则执行微任务,直至微任务队列清空。

如何理解事件循环

理解事件循环,得知道它为什么叫“事件循环”。这里可以借用MDN中的解释

之所以称之为事件循环,是因为它经常按照类似如下的方式被实现:

while (queue.waitForMessage()) {
  queue.processNextMessage();
}

image.png

函数的处理会一直进行到执行栈再次为空为止;然后事件循环将会处理队列中的下一个消息(如果还有的话)。

从代码理解task/microTask和事件循环

这里给两段简单的代码:

<script>
    console.log(1)
    Promise.resolve().then(()=>console.log(2))
</script>
<script>
    console.log(3)
</script>

打印顺序是123,一个script看成一个任务,任务执行完检查并执行微任务队列

setTimeout(()=>console.log(1))
setTimeout(()=>Promise.resolve().then(()=>console.log(2)))
setTimeout(()=>console.log(3))

打印顺序也是123:

  • 执行三个setTimeout;检查微任务队列(此时为空)。结束后消息队列有三个setTimeout的回调任务
  • 执行栈为空,消息队列取出第一个消息,放入执行栈,打印1;检查微任务队列(此时为空)
  • 执行栈为空,消息队列取出第二个消息,放入执行栈,执行Promise.resolve,往微任务队列放入了一个任务(console.log(2));检查并执行微任务队列,打印2
  • 执行栈为空,取出第三个消息,打印3