事件循环:微任务和宏任务

date
Jul 21, 2022
slug
24
status
Published
tags
Javascript
summary
简单理解Javascript的事件循环
type
Post
Book
浏览器中Javascript和Node.js的流程都是基于事件循环的。理解事件循环的工作方式对于代码优化很重要,有时对于正确的架构也很重要。
首先大家要理解为什么要有事件循环?JS是一门单线程的语言,所有的任务都是在一个线程上完成的,所以在遇到一些I/O,网络请求等特别费时的操作时,如果采用“同步模式”等待任务返回再继续执行的话,会使得执行特别耗时,执行效率特别低。
为了解决这个问题,于是就有了事件循环(Event Loop)这个概念。
引用MDN的描述,事件循环的概念就是:
每个代理都是由事件循环驱动的,事件循环负责收集用事件(包括用户事件以及其他非用户事件等)、对任务进行排队以便在合适的时候执行回调。然后它执行所有处于等待中的 JavaScript 任务(宏任务),然后是微任务,然后在开始下一次循环之前执行一些必要的渲染和绘制操作。

宏任务/微任务

宏任务可以被理解为每次"执行栈"中所执行的代码,而浏览器会在每次宏任务执行结束后,在下一个宏任务执行开始前,对页面进行渲染,而宏任务包括:
  • script(整体代码)
  • setTimeout
  • setInterval
  • I/O
  • UI交互事件
  • postMessage
  • MessageChannel
  • setImmediate
  • UI rendering
微任务,可以理解是在当前"执行栈"中的任务执行结束后立即执行的任务。而且早于页面渲染和取任务队列中的任务。微任务包括:
  • Promise.then
  • Object.observe
  • MutaionObserver
  • process.nextTick
他们的运行机制是这样的:
  • 执行一个宏任务(栈中没有就从事件队列中获取)
  • 执行过程中如果遇到微任务,就将它添加到微任务的任务队列中
  • 宏任务执行完毕后,立即执行当前微任务队列中的所有微任务(依次执行)
  • 当前宏任务执行完毕,开始检查渲染,然后GUI线程接管渲染
  • 渲染完毕后,JS线程继续接管,开始下一个宏任务(从事件队列中获取)
notion image
在了解了宏任务和微任务之后,整个Event Loop的流程图就可以用下面的流程图来概括:
notion image

效果检验

下方这段代码的输出是什么?
console.log(1);

setTimeout(() => console.log(2));

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

Promise.resolve().then(() => setTimeout(() => console.log(4)));

Promise.resolve().then(() => console.log(5));

setTimeout(() => console.log(6));

console.log(7);
输出结果
1 7 3 5 2 6 4。
结果解析
console.log(1);
// 第一行立即执行,它输出 `1`。
// 到目前为止,宏任务队列和微任务队列都是空的。

setTimeout(() => console.log(2));
// `setTimeout` 将回调添加到宏任务队列。
// - 宏任务队列中的内容:
//   `console.log(2)`

Promise.resolve().then(() => console.log(3));
// 将回调添加到微任务队列。
// - 微任务队列中的内容:
//   `console.log(3)`

Promise.resolve().then(() => setTimeout(() => console.log(4)));
// 带有 `setTimeout(...4)` 的回调被附加到微任务队列。
// - 微任务队列中的内容:
//   `console.log(3); setTimeout(...4)`

Promise.resolve().then(() => console.log(5));
// 回调被添加到微任务队列
// - 微任务队列中的内容:
//   `console.log(3); setTimeout(...4); console.log(5)`

setTimeout(() => console.log(6));
// `setTimeout` 将回调添加到宏任务队列
// - 宏任务队列中的内容:
//   `console.log(2); console.log(6)`

console.log(7);
// 立即输出 7
 

© LewisWong 2021 - 2025