宏任务(Macrotask)与微任务(Microtask)详解
在 JavaScript 的事件循环(Event Loop)机制中,任务分为两大类:宏任务和微任务。理解它们的区别是掌握异步编程的关键。
一、宏任务(Macrotask/Task)
宏任务是事件循环的基本单位,每次事件循环只执行一个宏任务(从队列中取出第一个)。执行完宏任务后,会清空所有微任务队列。
常见宏任务:
-
setTimeout()
- 定时器回调 -
setInterval()
- 间隔定时器回调 -
setImmediate()
- Node.js 特有(比setTimeout(fn, 0)
优先级高) -
I/O 操作:
- 文件读写(Node.js 的
fs
模块) - 网络请求(
fetch
/XMLHttpRequest
的回调)
- 文件读写(Node.js 的
- UI 渲染(浏览器环境)
-
DOM 事件回调:
-
click
,scroll
,resize
等事件处理器
-
-
requestAnimationFrame
- 浏览器动画回调 -
MessageChannel
消息通道 -
脚本整体代码(
<script>
标签内的同步代码)
二、微任务(Microtask)
微任务在每个宏任务结束后立即执行,且会清空整个微任务队列(包括执行过程中新产生的微任务)。
常见微任务:
-
Promise
回调:.then()
.catch()
.finally()
-
async/await
-await
后的代码相当于微任务 -
queueMicrotask()
- 专门的微任务 API -
MutationObserver
- DOM 变化观察器(浏览器) -
process.nextTick()
- Node.js 特有(优先级最高)
三、执行顺序图示
┌───────────────────────┐
│ 宏任务队列 │
│ (setTimeout, I/O等) │<──────────┐
└──────────┬────────────┘ │
│ 取出一个任务 │
▼ │
┌───────────────────────┐ │
│ 执行当前宏任务 │ │
└──────────┬────────────┘ │
│ 产生微任务 │
▼ │
┌───────────────────────┐ │
│ 清空所有微任务队列 │──────┐ │
│ (Promise, await等) │ │ │
└──────────┬────────────┘ │ │
│ │ │
▼ │ │
┌───────────────────────┐ │ │
│ 可选的UI渲染 │ │ │
└───────────────────────┘ │ │
│ │ │
└───────────────────┘ │
│
事件循环继续循环───────────┘
四、关键区别总结
特性 | 宏任务 | 微任务 |
---|---|---|
执行时机 | 事件循环每次取一个执行 | 在宏任务结束后立即全部执行 |
队列清空 | 每次只执行队列中第一个任务 | 清空整个队列(包括新产生的) |
优先级 | 低 | 高(在渲染前执行) |
阻塞风险 | 可能阻塞渲染 | 过长队列会阻塞渲染 |
典型API |
setTimeout , I/O, 事件 |
Promise , queueMicrotask
|
五、经典面试题分析
console.log('Start'); // 1. 同步宏任务
setTimeout(() => console.log('Timeout'), 0); // 宏任务
Promise.resolve()
.then(() => console.log('Promise 1')) // 微任务
.then(() => console.log('Promise 2')); // 微任务
queueMicrotask(() => console.log('Queue Microtask')); // 微任务
console.log('End'); // 2. 同步宏任务
执行顺序:
-
Start
(同步宏任务) -
End
(同步宏任务) -
Promise 1
(微任务) -
Queue Microtask
(微任务) -
Promise 2
(微任务) -
Timeout
(宏任务)
关键点:
- 所有微任务(包括链式调用产生的)会在当前宏任务结束后一次性执行
-
queueMicrotask
和Promise
都属于微任务 - 微任务执行顺序按入队顺序
六、特殊场景注意
-
Node.js 差异:
-
process.nextTick()
优先级高于 Promise -
setImmediate()
和setTimeout(fn, 0)
顺序不确定
-
-
浏览器渲染:
- 微任务在渲染前执行
- 宏任务在渲染后执行
button.addEventListener('click', () => { Promise.resolve().then(() => console.log('Microtask')); console.log('Listener'); });
点击后输出:
Listener
→Microtask
(然后才重绘) -
微任务嵌套:
Promise.resolve().then(() => { console.log('A'); Promise.resolve().then(() => console.log('B')); });
输出:
A
→B
(微任务队列会完全清空)