JavaScript 中浏览器与 Node.js 的 Event Loop 详细解析

Event Loop(事件循环)是 JavaScript 运行时环境(如浏览器和 Node.js)中处理异步操作的核心机制。它负责管理任务队列、微任务队列和回调函数的执行顺序,确保 JavaScript 的单线程模型能够高效处理异步任务。以下是对浏览器和 Node.js 中 Event Loop 的详细解释:
1. 浏览器的 Event Loop
浏览器的 Event Loop 是基于 HTML5 规范实现的,主要分为以下几个部分:
1.1 任务队列
- 宏任务(Macro Task):包括
setTimeout
、setInterval
、I/O
操作、UI 渲染、requestAnimationFrame
等。 - 微任务(Micro Task):包括
Promise.then
、MutationObserver
、queueMicrotask
等。
1.2 执行顺序
- 执行同步代码:所有同步代码会立即执行,直到调用栈为空。
- 执行微任务:检查微任务队列,依次执行所有微任务,直到微任务队列为空。
- 渲染页面:如果需要,浏览器会进行页面渲染(如重绘或重排)。
- 执行宏任务:从宏任务队列中取出一个任务执行。
- 循环:重复上述步骤,直到所有任务队列为空。
1.3 示例
console.log("Start");
setTimeout(() => {
console.log("Timeout");
}, 0);
Promise.resolve().then(() => {
console.log("Promise");
});
console.log("End");
输出顺序:
Start
End
Promise
Timeout
- 同步代码先执行(
Start
和End
)。 - 微任务(
Promise
)优先于宏任务(Timeout
)执行。
2. Node.js 的 Event Loop
Node.js 的 Event Loop 是基于 libuv 库实现的,与浏览器的 Event Loop 类似,但有一些区别。Node.js 的 Event Loop 分为多个阶段,每个阶段处理不同类型的任务。
2.1 阶段
- Timers:执行
setTimeout
和setInterval
的回调。 - Pending Callbacks:执行系统操作(如 TCP 错误)的回调。
- Idle, Prepare:内部使用,开发者无需关注。
- Poll:
- 检索新的 I/O 事件。
- 执行 I/O 相关的回调(如文件读取、网络请求)。
- 如果 Poll 队列为空,会检查是否有定时器到期,如果有则跳转到 Timers 阶段。
- Check:执行
setImmediate
的回调。 - Close Callbacks:执行关闭事件的回调(如
socket.on('close')
)。
2.2 微任务
- Node.js 中的微任务包括
Promise.then
和process.nextTick
。 process.nextTick
的优先级高于Promise.then
。
2.3 示例
console.log("Start");
setTimeout(() => {
console.log("Timeout");
}, 0);
setImmediate(() => {
console.log("Immediate");
});
Promise.resolve().then(() => {
console.log("Promise");
});
process.nextTick(() => {
console.log("Next Tick");
});
console.log("End");
输出顺序:
Start
End
Next Tick
Promise
Timeout
Immediate
- 同步代码先执行(
Start
和End
)。 process.nextTick
优先于Promise.then
。setTimeout
和setImmediate
的顺序可能不确定(取决于事件循环的启动时间)。
3. 浏览器与 Node.js 的区别
- 微任务优先级:
- 浏览器:
Promise.then
和MutationObserver
是微任务。 - Node.js:
process.nextTick
优先级高于Promise.then
。
- 浏览器:
- 阶段划分:
- 浏览器:没有明确的阶段划分,只有宏任务和微任务。
- Node.js:有明确的阶段(如 Timers、Poll、Check 等)。
- API 支持:
- 浏览器:支持
requestAnimationFrame
。 - Node.js:支持
setImmediate
和process.nextTick
。
- 浏览器:支持
4. 最佳实践
- 避免阻塞 Event Loop:
- 避免长时间运行的同步代码。
- 将 CPU 密集型任务拆分为小块,或使用 Worker 线程。
- 合理使用微任务:
- 微任务会在当前任务结束后立即执行,适合处理高优先级的任务。
- 理解任务优先级:
- 在 Node.js 中,
process.nextTick
的优先级最高。 - 在浏览器中,
Promise.then
优先于setTimeout
。
- 在 Node.js 中,
通过深入理解 Event Loop,你可以更好地优化代码性能,避免常见的异步陷阱(如回调地狱、任务阻塞等),并编写出高效、可维护的 JavaScript 代码。