事件循环:微任务和宏任务
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线程继续接管,开始下一个宏任务(从事件队列中获取)
在了解了宏任务和微任务之后,整个Event Loop的流程图就可以用下面的流程图来概括:
效果检验
下方这段代码的输出是什么?
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