【浏览器机制】事件循环(EventLoop)与宏微任务

2021/09/09 14:38:03

任务队列

规范open in new window中可知:

一个事件循环可以有多个任务队列,任务队列是一组任务(A task queue is a set of tasks

每个宏任务之后,引擎会立即执行微任务队列中的所有任务,然后再执行其他的宏任务,或渲染,或进行其他任何操作。这样确保了微任务之间的应用程序环境基本相同(没有鼠标坐标更改,没有新的网络数据等)

宏任务和微任务

Js 有两种任务的执行模式:同步模式(Synchronous)和异步模式(Asynchronous)。

创建异步任务主要分为宏任务与微任务两种。

ES6 规范中,宏任务(Macrotask) 称为 Task, 微任务(Microtask) 称为 Jobs。宏任务是由宿主(浏览器、Node)发起的,而微任务由 JS 自身发起。

宏任务与微任务的几种创建方式

宏任务(Macrotask)微任务(Microtask)
setTimeoutrequestAnimationFrame(有争议)
setIntervalMutationObserver(浏览器环境)
MessageChannelPromise.[ then/catch/finally ]
I/O,事件队列process.nextTick(Node 环境)
setImmediate(Node 环境)queueMicrotask
script(整体代码块)
  • script 标签

    如果同时存在两个 script 代码块,会首先在执行第一个 script 代码块中的同步代码,如果这个过程中创建了微任务并进入了微任务队列,第一个 script 同步代码执行完之后,会首先去清空微任务队列,再去开启第二个 script 代码块的执行。

    <script>
      console.log("script 1");
      setTimeout(() => {
        console.log("script 1 宏任务");
      });
      queueMicrotask(() => {
        console.log("script 1 微任务");
      });
    </script>
    <script>
      console.log("script 2");
      setTimeout(() => {
        console.log("script 2 宏任务");
      });
      queueMicrotask(() => {
        console.log("script 2 微任务");
      });
    </script>
    

    执行结果如下(chrome 109.0.5414.120):

    script 1
    script 1 微任务
    script 2
    script 2 微任务
    script 1 宏任务
    script 2 宏任务
    
  • setTimeout 方法

    setTimeout 方法是在指定的时间之后将回调推入宏任务队列,而不是在指定时间后执行,需注意这点区别,这也是 js 定时器不准的原因。

EventLoop(事件循环)

一次事件循环开始后先执行最早的一个宏任务,执行完毕后依次执行所有的微任务,以此循环往复。

EventLoop 循环示意图

EventLoop循环示意图

事件循环步骤

  1. 宏任务 队列(例如 “script”)中出队(dequeue)并执行最早的宏任务。
  2. 执行所有 微任务
    • 当微任务队列非空时:
      • 出队(dequeue)并执行最早的微任务。
  3. 执行渲染,如果有。
  4. 如果宏任务队列为空,则休眠直到出现宏任务。
  5. 转到步骤 1。

因为首次执行宏队列中会有 script(整体代码块)任务,所以实际上就是 Js 解析完成后,在异步任务中,会先执行完所有的微任务,这里也是很多面试题喜欢考察的。需要注意的是,新创建的微任务会立即进入微任务队列排队执行,不需要等待下一次轮回。

setTimeout(() => {
  console.log(1);
});

queueMicrotask(() => {
  console.log(2);
});

Promise.resolve(3).then((res) => {
  console.log(res);
});

// 2 3 1

这个例子中 setTimeout 创建的是宏任务,queueMicrotask 和 Promise.resolve 创建的是微任务,宏任务在微任务之后执行,微任务执行顺序取决于谁先被推入微任务队列