微任务和宏任务是 JavaScript 解决异步执行顺序的核心机制,基于「事件循环(Event Loop)」实现,目的是合理规划异步代码的执行优先级,避免高优先级异步操作被阻塞。
一、核心定义:什么是微任务和宏任务?
JavaScript 异步任务分为两大队列,二者执行优先级和场景有明确区别:
1. 宏任务(Macrotask / Task)
-
核心定义:属于「宏观」异步任务,执行优先级较低,每次事件循环仅执行一个宏任务(或一个宏任务队列的批量任务)。
-
本质特点:任务执行耗时相对较长(或执行时机较晚),会触发浏览器渲染 / UI 更新,不同宏任务之间会穿插浏览器的渲染流程。
-
常见宏任务类型:
宏任务类型 说明 setTimeout延迟指定时间执行回调(最小延迟约 4ms) setInterval按指定时间间隔重复执行回调 setImmediate(Node.js 环境)立即执行异步回调,优先级低于 process.nextTickrequestAnimationFrame浏览器重绘前执行(与屏幕刷新率同步,属于浏览器宏任务) I/O 操作 (Node.js 环境)文件读取、网络请求等异步 I/O 脚本执行(主代码块) 整个 JS 主程序代码(同步代码)属于第一个宏任务 UI 交互事件 点击、滚动、输入等浏览器事件回调(如 click、scroll)
2. 微任务(Microtask / Job)
-
核心定义:属于「微观」异步任务,执行优先级极高,每次宏任务执行完毕后,会立即清空当前所有微任务队列(按顺序执行所有微任务),再进入下一轮事件循环。
-
本质特点:任务执行耗时短,不触发浏览器渲染,优先于任何宏任务执行,保证高优先级异步操作(如 Promise 回调)快速执行。
-
常见微任务类型:
微任务类型 说明 Promise.then()/Promise.catch()/Promise.finally()Promise 状态改变后的回调( Promise构造函数内是同步代码)process.nextTick(Node.js 环境)优先级最高的微任务,优先于所有其他微任务 MutationObserver(浏览器环境)监听 DOM 变化的回调 queueMicrotask()手动创建微任务(ES6 标准,浏览器 / Node.js 均支持)
二、核心执行规则:事件循环(Event Loop)流程
事件循环是 JS 执行异步任务的核心流程,微任务和宏任务的执行顺序遵循「先同步、后异步;先微任务、后宏任务」的原则,具体步骤如下:
1. 浏览器环境事件循环流程
- 执行「当前宏任务队列」中的第一个宏任务(默认是主代码块/同步代码);
- 执行完毕后,立即清空「当前微任务队列」中的所有微任务(按添加顺序依次执行);
- 微任务执行完毕后,浏览器进行「UI 渲染/重绘」(若有 DOM 变化);
- 进入下一轮事件循环,取出「下一个宏任务队列」中的任务重复步骤 1-3;
- 循环往复,直到所有宏任务和微任务执行完毕。
2. 关键补充(Node.js 环境差异)
Node.js 事件循环分为 6 个阶段(timers、pending callbacks 等),微任务会在「每个阶段执行完毕后」清空,且 process.nextTick 优先级高于 Promise 微任务,但核心规则仍遵循「宏任务 → 微任务 → 下一个宏任务」。
3. 通俗理解
可以把事件循环比作「餐厅点餐流程」:
- 同步代码 = 顾客当场点的菜(优先制作);
- 微任务 = 顾客加的「小菜」(当前菜做完后,立即制作,无需等下一桌);
- 宏任务 = 下一桌顾客的订单(当前桌所有菜品(同步 + 小菜)上完后,再接待下一桌);
- 浏览器渲染 = 服务员清理餐桌(微任务做完后,清理完再接待下一桌)。
三、代码示例:直观理解执行顺序
示例 1:基础顺序(同步 → 微任务 → 宏任务)
// 1. 同步代码(第一个宏任务的核心)
console.log('① 同步代码执行');
// 宏任务:setTimeout
setTimeout(() => {
console.log('④ 宏任务:setTimeout 执行');
}, 0);
// 微任务:Promise.then
Promise.resolve().then(() => {
console.log('③ 微任务:Promise.then 执行');
});
// 微任务:queueMicrotask
queueMicrotask(() => {
console.log('② 微任务:queueMicrotask 执行');
});
// 执行结果顺序:① → ② → ③ → ④
// 解析:同步代码先执行 → 清空所有微任务(按添加顺序) → 执行宏任务
示例 2:嵌套异步(微任务内嵌套微任务)
console.log('① 同步代码');
setTimeout(() => {
console.log('⑤ 宏任务:外层 setTimeout');
// 宏任务内的微任务
Promise.resolve().then(() => {
console.log('⑥ 宏任务内的微任务:Promise.then');
// 微任务内嵌套微任务(仍会在当前微任务队列清空)
queueMicrotask(() => {
console.log('⑦ 微任务内嵌套的微任务');
});
});
}, 0);
Promise.resolve().then(() => {
console.log('② 微任务:外层 Promise');
// 微任务内嵌套宏任务
setTimeout(() => {
console.log('⑧ 微任务内嵌套的宏任务');
}, 0);
});
queueMicrotask(() => {
console.log('③ 微任务:queueMicrotask 1');
// 微任务内嵌套微任务
queueMicrotask(() => {
console.log('④ 微任务:queueMicrotask 2');
});
});
// 执行结果顺序:① → ② → ③ → ④ → ⑤ → ⑥ → ⑦ → ⑧
// 解析:
// 1. 同步代码执行(①)
// 2. 清空外层微任务队列:② → ③ → ④(嵌套微任务仍在当前队列)
// 3. 执行第一个宏任务(⑤)
// 4. 清空该宏任务内的微任务队列:⑥ → ⑦(嵌套微任务也会被清空)
// 5. 执行下一个宏任务(⑧,由微任务嵌套添加)
示例 3:Promise 构造函数的同步性
console.log('① 同步代码');
new Promise((resolve) => {
console.log('② Promise 构造函数内(同步)');
resolve(); // 改变状态(同步操作)
}).then(() => {
console.log('④ 微任务:Promise.then');
});
setTimeout(() => {
console.log('⑤ 宏任务:setTimeout');
}, 0);
console.log('③ 同步代码结束');
// 执行结果顺序:① → ② → ③ → ④ → ⑤
// 解析:Promise 构造函数内是同步代码,then 回调才是微任务
四、核心区别:微任务 vs 宏任务
| 对比维度 | 微任务(Microtask) | 宏任务(Macrotask) |
|---|---|---|
| 执行优先级 | 高(宏任务执行后立即执行) | 低(微任务队列清空后才执行) |
| 执行时机 | 同一宏任务执行完毕后,一次性清空所有微任务 | 每轮事件循环执行一个 / 一批宏任务,之间穿插渲染 |
| 是否触发渲染 | 不触发(微任务执行期间浏览器不渲染) | 不同宏任务之间会触发浏览器渲染 |
| 执行数量 | 一次性执行所有待执行微任务 | 每轮事件循环执行一个(或一个队列的批量任务) |
| 常见类型 | Promise.then、queueMicrotask、process.nextTick(Node) | setTimeout、setInterval、I/O、UI 事件、主代码块 |
| 嵌套执行 | 微任务内嵌套的微任务,仍在当前微任务队列执行 | 宏任务内嵌套的宏任务,进入下一轮事件循环队列 |
五、典型应用场景
1. 微任务的应用场景
-
Promise 异步回调:接口请求成功后的数据处理、状态更新(优先执行,保证数据快速同步);
// 接口请求(异步),成功后用 then 处理(微任务,优先执行) fetch('/api/user') .then(res => res.json()) .then(data => console.log('数据处理:', data)); -
DOM 变化后的后续操作:用
MutationObserver监听 DOM 变化后,立即执行后续逻辑(无需等待宏任务); -
手动插队异步任务:用
queueMicrotask将任务插入微任务队列,优先于宏任务执行; -
Node.js 中高优先级异步:
process.nextTick用于核心模块的异步回调,保证优先级最高。
2. 宏任务的应用场景
-
延迟执行:
setTimeout用于非紧急的延迟操作(如提示框自动关闭、防抖函数);// 防抖函数中的延迟执行(宏任务) function debounce(fn, delay) { let timer = null; return () => { clearTimeout(timer); timer = setTimeout(fn, delay); }; } -
定时重复执行:
setInterval用于轮询接口、定时更新数据; -
浏览器渲染同步:
requestAnimationFrame用于动画效果,保证与屏幕刷新率同步; -
UI 交互响应:点击、滚动等事件回调(宏任务,避免频繁触发阻塞页面)。
六、常见误区
setTimeout(fn, 0)立即执行:误区:认为延迟 0ms 会立即执行;正解:setTimeout是宏任务,即使延迟 0ms,也会等待同步代码和所有微任务执行完毕后,才会执行。- 微任务和宏任务的嵌套顺序:误区:微任务内嵌套的宏任务会立即执行;正解:微任务内嵌套的宏任务会进入下一轮事件循环队列,需等待当前所有微任务和本轮宏任务执行完毕后才会执行。
- Promise 全是异步:误区:认为 Promise 所有代码都是异步;正解:
Promise构造函数内的代码是同步执行的,只有then/catch/finally回调是微任务(异步)。
总结
- 核心顺序:同步代码 → 微任务(全部执行) → 宏任务(逐个执行) → 浏览器渲染 → 下一轮事件循环;
- 优先级:微任务 > 宏任务,微任务内嵌套的微任务优先于任何宏任务;
- 本质作用:微任务保证高优先级异步操作快速执行,宏任务规划低优先级异步操作,避免阻塞页面;
- 关键区分:Promise.then、queueMicrotask 是微任务;setTimeout、setInterval 是宏任务,Promise 构造函数内是同步代码。