Javascript 中事件环以及宏任务微任务详细介绍
在 JavaScript 中,宏任务(MacroTask
) 和 微任务(MicroTask
) 是事件循环中任务调度的核心分类。
一、概述:事件环工作执行流程
第一步:执行同步代码
从调用栈中执行当前所有同步任务,直到调用栈清空。
第二步:清空微任务队列
依次执行微任务队列中的所有任务(包括执行过程中新产生的微任务),直到队列为空。
第三步:渲染更新(浏览器)
如果需要,浏览器会执行页面渲染(重绘、布局等)。
第四步:执行一个宏任务
从宏任务队列中取出队列头的第一个任务(如 setTimeout 回调),执行它。
第五步:重复循环
回到第一步,开启新的事件循环。
二、宏任务(MacroTask
)
宏任务代表需要较长时间执行的异步任务,每次事件循环中只执行一个宏任务(队列中的首个):
常见的宏任务:
setTimeout
: 定时器回调函数(即使延迟为 0,也是宏任务)。
setInterval
: 周期性定时器回调。
I/O 操作
: 文件读写、网络请求(如 XMLHttpRequest、fetch 的回调)。
DOM 事件
: 用户交互事件(如点击、滚动等)的回调。
requestAnimationFrame
: 浏览器动画帧回调(通常归类为宏任务,但执行时机与渲染相关)。
script
: 整体代码 初始执行的全局同步代码本身也是一个宏任务。
MessageChannel
: 跨文档通信或 Web Worker 通信的回调。
Node.js 特有:
setImmediate
: Node.js 中立即执行的宏任务(与 setTimeout(fn, 0) 类似,但有差异)。
I/O 回调 Node.js 中文件、网络等异步操作的回调。
二、微任务(MicroTask
)
微任务具有更高优先级,会在当前宏任务执行后、下一个宏任务执行前
一次性清空队列。
常见的微任务:
Promise.then/catch/finally
: Promise 的异步回调(包括 async/await
的隐式转换)。
MutationObserver
: 监听 DOM
变化的回调(浏览器环境)。
queueMicrotask
: 显式将函数加入微任务队列的 API(如 queueMicrotask(() => { … }))。
Node.js 特有 :
process.nextTick Node.js 中优先级最高的微任务(甚至高于 Promise)
。
三、关键区别
四、执行顺序案例
1:基础顺序
console.log("Start"); // 同步代码(宏任务)
// 宏任务
setTimeout(() => console.log("Timeout"));
// 微任务
Promise.resolve().then(() => console.log("Promise"));
console.log("End"); // 同步代码(宏任务)
// 输出:
Start → End → Promise → Timeout
2:微任务嵌套
// 宏任务
setTimeout(() => console.log("Timeout"));
// 微任务(嵌套微任务)
Promise.resolve()
.then(() => {
console.log("Promise 1");
Promise.resolve().then(() => console.log("Nested Promise"));
})
.then(() => console.log("Promise 2"));
// 输出:
Promise 1 → Nested Promise → Promise 2 → Timeout
五、特殊注意事项
Node.js 与浏览器的差异:
Node.js 中 process.nextTick
的优先级高于 Promise.then。
Node.js 的 setImmediate 和 setTimeout(fn, 0)
执行顺序可能不同
。
微任务可能阻塞渲染:
如果微任务队列过长,浏览器会在执行完所有微任务后才进行渲染,导致页面卡顿。
避免微任务无限递归
:
function infiniteMicrotask() {
Promise.resolve().then(infiniteMicrotask);
}
infiniteMicrotask(); // 会导致页面卡死
六、总结
宏任务:用于处理需要延迟执行或与外部交互的任务(如定时器、I/O)。
微任务:用于处理需要立即执行的高优先级任务(如 Promise 回调)。
以上内容如有错误之处,欢迎批评指正