浏览器事件循环与微任务笔记
俊宁 · 日期未详整理自原官网的历史学习笔记,保留原有译文、引用和署名,不把资料内容认作本人研究或实践成果。本篇有格式适配或明确勘误,详见官网迁移清单。
查看官网迁移清单
查看官网迁移清单

微任务队列
Promise 处理程序
.then(() => { ... })
.catch(() => { ... })
.finally(() => { ... })
await 后的 async 函数体
async function asyncFunction() {
await ...
// Function body execution following await
}
queueMicrotask() 回调
queueMicrotask(() => {... })
new MutationObserver 回调
const observer = new MutationObserver(() => {... })
浏览器中的任务与微任务
以下以浏览器为范围;Node.js 的事件循环阶段和 nextTick 队列不直接套用此顺序。
| 特性 | 宏任务 | 微任务 |
|---|---|---|
| 优先级 | 低(下一个事件循环) | 高(当前宏任务结束后立即执行) |
| 典型场景 | 定时器、用户交互事件 | Promise、DOM 变更监听 |
| 队列处理 | 每次事件循环处理一个 | 每次宏任务结束后清空全部 |
关键点:理解事件循环的“当前任务中的同步代码 → 清空微任务 → 按需更新渲染 → 后续任务”流程是掌握异步编程的核心。
宏任务(Macrotask)
任务由运行脚本、用户交互或计时器等机制排入任务队列。这里列举浏览器中的常见来源:
setTimeout、setInterval- 用户交互事件
渲染更新不是这里所说的普通任务;浏览器在合适时机决定是否更新渲染。
微任务(Microtask)
需在当前宏任务结束后立即执行的任务,优先级高于下一个宏任务。常见类型包括:
Promise.then、Promise.catch、Promise.finallyMutationObserver(DOM 变更监听)queueMicrotask
事件循环(Event Loop)的优先级规则
执行顺序流程:
- 同步代码:优先执行主线程的同步任务(如 console.log)。
- 微任务队列:当前宏任务执行完毕后,立即清空所有微任务(包括执行过程中新产生的微任务)。
- 渲染阶段(浏览器环境):检查是否需要 UI 渲染(如 DOM 更新)。
- 下一个宏任务:从宏任务队列中取出一个任务执行,重复上述流程。
核心规则:
- 微任务优先级高于宏任务:当前宏任务结束后,必须先执行完所有微任务,才能执行下一个宏任务。
- 新微任务继续排队:微任务执行过程中产生的新微任务会加入队尾,在本次微任务检查点继续处理,直到队列清空;不会打断当前正在执行的回调。
代码示例与执行顺序解析
console.log("1"); // 同步任务
setTimeout(() => console.log("2"), 0); // 宏任务
Promise.resolve().then(() => console.log("3")); // 微任务
console.log("4"); // 同步任务
输出顺序:1 → 4 → 3 → 2
解释:
- 同步任务输出 1 和 4。
- 当前宏任务(主线程)结束后,执行微任务队列中的
Promise.then,输出 3。 - 最后执行宏任务队列中的
setTimeout,输出 2。
常见场景与陷阱
1、嵌套任务
若任务中产生微任务,任务回调执行完毕并交还控制后,微任务才会在相应检查点执行:
setTimeout(() => {
console.log("宏任务");
Promise.resolve().then(() => console.log("微任务"));
}, 0);
输出顺序为:宏任务 → 微任务。
2、微任务阻塞渲染
长时间运行的微任务会延迟 UI 渲染,因为微任务队列必须在渲染前清空。