image.png
event loop (brower) - 图2
如图,主线程运行的时候,会产生堆(heap)和栈(stack),其中堆为内存、栈为函数调用栈。我们能看到,Event Loop 负责执行代码、收集和处理事件以及执行队列中的子任务,具体包括以下过程。

  1. JavaScript 有一个主线程和调用栈,所有的任务最终都会被放到调用栈等待主线程执行。
  2. 同步任务会被放在调用栈中,按照顺序等待主线程依次执行。
  3. 主线程之外存在一个回调队列,回调队列中的异步任务最终会在主线程中以调用栈的方式运行。
  4. 同步任务都在主线程上执行,栈中代码在执行的时候会调用浏览器的 API,此时会产生一些异步任务。
  5. 异步任务会在有了结果(比如被监听的事件发生时)后,将异步任务以及关联的回调函数放入回调队列中。
  6. 调用栈中任务执行完毕后,此时主线程处于空闲状态,会从回调队列中获取任务进行处理。

上述过程会不断重复,这就是 JavaScript 的运行机制,称为事件循环机制(Event Loop)。

微任务

microTask
promise process.nextTick MutationObserver

宏任务

macroTask
setTimeout setInterval script setImmediate(可能会被废弃,非标准实现) I/0 requestAnimationFrame
UI 渲染(浏览器)、

为什么要将异步任务分为宏任务和微任务呢?
这是为了避免回调队列中等待执行的异步任务(宏任务)过多,导致某些异步任务(微任务)的等待时间过长。在每个宏任务执行完成之后,会先将微任务队列中的任务执行完毕,再执行下一个宏任务。

在浏览器的异步回调队列中,宏任务和微任务的执行过程如下:

  1. 宏任务队列一次只从队列中取一个任务执行,执行完后就去执行微任务队列中的任务。
  2. 微任务队列中所有的任务都会被依次取出来执行,直到微任务队列为空。
  3. 在执行完所有的微任务之后,执行下一个宏任务之前,浏览器会执行 UI 渲染操作、更新界面。

在浏览器中每个宏任务执行完成后,会执行微任务队列中的任务。而在 Node.js 中,事件循环分为 6 个阶段,微任务会在事件循环的各个阶段之间执行。也就是说,每当一个阶段执行完毕,就会去执行微任务队列的任务。
这一次,彻底弄懂 JavaScript 执行机制