前言

这段时间看了很多讲解事件循环、Macrotask和Microtask的文章,但是有很多细节文章里都没讲明白,本文主要对我的一些疑问进行了总结,比如事件循环怎么才叫完成了一次事件循环,还有任务和微任务之间的关系是怎么样的。

事件循环(Event loops)

基本概念

每个代理都是由事件循环驱动的,事件循环负责收集用事件(包括用户事件以及其他非用户事件等)、对任务进行排队以便在合适的时候执行回调。然后它执行所有处于等待中的 JavaScript 任务(宏任务),然后是微任务,然后在开始下一次循环之前执行一些必要的渲染和绘制操作。

事件循环的职责

  • 收集事件
  • 维护任务队列
  • 维护微任务队列
  • 调用渲染引擎

一次完整的事件循环迭代(运行环境Chrome 87)

  • 事件循环迭代开始
  • 执行任务队列中的第一个任务
  • 依次执行微任务队列中所有的微任务
  • 第一个任务退出任务队列
  • 必要的渲染和绘制操作
  • 重复以上步骤直到任务队列清空
  • 事件循环迭代结束

这里的任务队列指的是事件循环开始迭代时的任务队列

在事件循环开始迭代之后加入到队列中的任务需要在下一次迭代开始之后才会被执行。

宏任务(Macrotasks)和微任务(Microtasks)

Macrotasks通常指的是Tasks(下文有介绍),Macrotasks在标准上是查不到任何相关描述的,这个概念从何而来不得而知,但是业界普遍承认这个概念以跟Microtasks做区分。

Microtasks是写入HTML Standard的标准,并且在浏览器环境有相关api queueMicrotask

实际上浏览器中只有任务(Tasks)微任务(Microtasks)之分,下面来讲讲他们的概念和区别。

任务(Tasks)和微任务(Microtasks)的概念和关系

任务(Tasks)
基本概念
一个 任务 就是由执行诸如从头执行一段程序、执行一个事件回调或一个 interval/timeout 被触发之类的标准机制而被调度的任意 JavaScript 代码。这些都在 任务队列(task queue)上被调度。
任务的添加

在以下时机,任务会被添加到任务队列:

  • 一段新程序或子程序被直接执行时(比如从一个控制台,或在一个 <script> 元素中运行代码)。
  • 触发了一个事件,将其回调函数添加到任务队列时。
  • 执行到一个由 setTimeout()setInterval() 创建的 timeout 或 interval,以致相应的回调函数被添加到任务队列时。
setTimeout和setInterval

当调用setTimeout(fn, time)setInterval(fn, time)添加任务时,第二个参数time指的是任务被添加到任务队列的时间,而不是fn被执行的时间,fn被执行的时间应该是被添加的任务执行的时间。

微任务(Microtasks)
基本概念
一个 微任务(microtask)就是一个简短的函数,当创建该函数的函数执行之后,并且只有当 Javascript 调用栈为空,而控制权尚未返还给被 user agent 用来驱动脚本执行环境的事件循环之前,该微任务才会被执行。 事件循环既可能是浏览器的主事件循环也可能是被一个 web worker 所驱动的事件循环。这使得给定的函数在没有其他脚本执行干扰的情况下运行,也保证了微任务能在用户代理有机会对该微任务带来的行为做出反应之前运行。
微任务的添加
JavaScript 中的 promisesMutation Observer API 都使用微任务队列去运行它们的回调函数,但当能够推迟工作直到当前事件循环过程完结时,也是可以执行微任务的时机。为了允许第三方库、框架、polyfills 能使用微任务,Window 暴露了 queueMicrotask() 方法,而 Worker 接口则通过WindowOrWorkerGlobalScope mixin 提供了同名的 queueMicrotask() 方法。
微任务存在的意义

当你想在一次任务结束之前执行一些代码时(而不是下一次任务再执行),你可以使用微任务,具体的使用场景可以看看何时使用微任务

任务和微任务之间的区别
  • 当执行来自任务队列中的任务时,在每一次新的事件循环开始迭代的时候运行时都会执行队列中的每个任务。在每次迭代开始之后加入到队列中的任务需要在下一次迭代开始之后才会被执行
  • 每次当一个任务执行上下文为空的时候,微任务队列中的每一个微任务会依次被执行。不同的是它会等到微任务队列为空才会停止执行——即使中途有微任务加入。换句话说,微任务可以添加新的微任务到队列中,并在当前任务结束之前且下一个任务开始执行之前执行完所有的微任务。
任务和微任务之间的关系

在写这篇文章的时候我有一个疑问,既然在一个任务执行上下文为空的时候,再执行微任务队列中的微任务。那微任务是在当前被执行任务离开任务队列之前执行还是之后执行?这就关系到一个任务的执行时间应不应该包括微任务的执行时间,以及事件循环的执行顺序。

我查了一些资料,在HTML StandardECMA标准里好像并没有明确定义任务和微任务队列应该是包含关系还是相互独立的关系,所以任务和微任务之间的关系应该是浏览器厂商自己去实现的

我这里只测试了Chrome浏览器(版本号是87.0.4280.66)

image.png

可以看到Chrome是把Run Microtasks的执行时间算在了Task的执行时间里的,所以在Chrome环境里任务和微任务是包含关系,也就是说当一个微任务执行时,必定有一个任务在同时执行。


参考资源


spike
248 声望5 粉丝

« 上一篇
属性描述符