微任务和宏任务的执行顺序是面试中经常会被问到的问题,这里就来解析一下。
下面的代码中,打印的顺序是什么呢?
1 | console.log("script 1"); |
答案是:
1 | [Log] script 1 |
解析
JavaScript 是单线程的,它的任务调度是基于事件循环的。事件循环中有两种任务队列:微任务队列和宏任务队列。
timeout 中的回调函数会被放到宏任务队列中,而 promise 中的回调函数会被放到微任务队列中。
上面代码的的执行顺序如下:
stateDiagram-v2
MTQ: 宏任务队列
Script: script 宏任务
note right of Script: 整个 script 作为一个宏任务
S1: console.log("script 1")
T1: setTimeout()
P1: Promise.resolve().then()
P2: Promise.resolve().then()
T2: setTimeout()
S2: console.log("script 2")
MicrotaskQ:微任务队列
PL1: () => console.log("promise 1")
PT: () => setTimeout(() => console.log("timeout in promise"))
PL2: () => console.log("promise 2")
PTL: () => console.log("timeout in promise")
SL1: () => console.log("timeout 1")
SL2: () => console.log("timeout 2")
state MTQ {
[*] --> Script
PL2 --> SL1
SL1 --> SL2
SL2 --> PTL
}
state Script {
[*] --> S1
S1 --> T1
T1 --> P1
note right of T1: 安排 () => console.log("timeout 1") 到宏任务队列
P1 --> P2
note right of P1: 安排 () => console.log("promise 1") 到微任务队列
P2 --> T2
note right of P2: 安排 () => console.log("promise 2") 到微任务队列
T2 --> S2
note right of T2: 安排 () => console.log("timeout 2") 到宏任务队列
}
S2 --> MicrotaskQ
state MicrotaskQ {
[*] --> PL1
PL1 --> PT
PT --> PL2
note left of PT: 安排 () => console.log("timeout in promise") 到宏任务队列
}
宏任务有哪些
- script
- setTimeout
- setInterval
- setImmediate
- requestIdleCallback
微任务有哪些
- Promise 的 then 和 catch
- process.nextTick (Node.js 环境)
- MutationObserver (浏览器环境)
补充说明
宏任务 Macrotask 不是标准的术语, HTML Standard 中的定义是:Task。
Task 和 Macrotask 、 UI rendering 的执行顺序
flowchart LR subgraph Event Loop Task --> Macrotask1 subgraph Macrotask Queue Macrotask1 --> Macrotask2 end Macrotask2 --> A["requestAnimationFrame() 的回调"] A --> UR[渲染 UI] UR --> Task end