Macrotask 宏任务
Macrotasks 包含了解析 HTML、生成 DOM、执行主线程 JS 代码和其他事件如 页面加载、输入、网络事件、定时器事件等。从浏览器的角度,Macrotask 代表的是一些离散的独立的工作。
常见应用
setTimeout
, setInterval
, setImmediate
, requestAnimationFrame
, I/O
, UI rendering
Microtask 微任务
Microtasks 则是为了完成一些更新应用程序状态的较小的任务,如处理 Promise 的回调和 DOM 的修改,以便让这些任务在浏览器重新渲染之前执行。Microtask 应该以异步的方式尽快执行,所以它们的开销比 Macrotask 要小,并且可以使我们在 UI 重新渲染之前执行,避免了不必要的 UI 渲染。
常见应用
process.nextTick
, Promises
, Object.observe
, MutationObserver
执行顺序
Event Loop 的实现需要至少一个 Macrotask Queue 和至少一个 Microtask Queue。为了便于理解,我们都简化成一个。
简单来说,Microtask Queue 具有更高的优先级,即执行一个 Macrotask 任务后,就会去清空整个 Microtask Queue,此时如果有新的 Microtask 加入也会被执行。
所以我们来看下面的代码:
1 console. Log( ' script start' ) 2 3 setTimeout ( function(){ 4 console. log( ' setTimeout' 5 },0 6 7 Promise. resolve( ).then( function(){ 8 console. log('promise1) 9 }). then( function(){ 10 console. Log( promise2") 11 }) 12 13 console. Log('script end'
执行顺序是什么?
我们已经知道 setTimeout 是 Macrotask,Promise 是 Microtask,而这段代码从上到下执行也是一个 Macrotask
步骤:
- 开始执行,执行脚本作为一个任务进入 Macrotask Queue,同时进入调用栈执行
- Line 1, 输出
script start
- Line 3 的 setTimeout 回调进入 Macrotask Queue 等待
- Line 7 的回调进入 Microtask Queue 等待
- Line 13 输出
script end
,此时脚本执行完成(即完成了一个 Macrotask) - 开始执行 Microtask Queue,从中拿出一个放入调用栈执行
- 开始执行 Line 7 的回调,该回调输出
promise1
,返回 undefined - Line 9 的回调进入 Microtask Queue,由于 Microtask Queue 没有清空,直接执行该回调,输出
promise2
,该回调返回 undefined - Microtask Queue 已清空(此时浏览器可以更新渲染UI),开始将 Macrotask Queue 中任务放入调用栈执行
- 执行 Line 3 的回调,输出
setTimeout
,Macrotask Queue 清空 - 程序执行完成
所以打印顺序为:
script start -> script end -> promise1 -> promise2 -> setTimeout
结论
- 微任务比宏任务具有更高的优先级
- JS 代码执行本身也是一个 Macrotask
- Macrotask 总是在 JS 代码执行完成并且 Microtask Queue 清空之后执行