js 中的事件循环与 node.js 中的事件循环有什么不同
浏览器
特点
- 单线程: 为了简化模型,避免错误。更因为渲染是一个连 续的过程,不能并发。
- 任务队列: 使用一个宏任务队列和多个微任务队列。
- 渲染时机:每次事件循环后会检查是否需要渲染页面
微任务
- Promise.then/catch/finally
- MutationObserver
- queueMicrotask
微任务机制是 JavaScript 事件循环中非常重要的设计,它使得开发者能够更精确地控制异步代码的执行时机,特别是在需要快速响应用户交互或更新 UI 时。
宏任务
- setTimeout
- setInterval
- I/O 比如:xhr, fs, fetch(本身是宏任务,回调是微任务队列)
- UI 渲染
Node.js
使用 libuv 库来处理事件循环。Node.js 的事件循环有明确的 6 个阶段。
- timers: 执行 setTimeout 和 setInterval 的回调
- pending callbacks: 执行一些系统操作的回调
- idle, prepare: 供内部使用
- poll: 获取新的 I/O 事件,执行与 I/O 相关的回调
- check: 执行 setImmediate 的回调
- close callbacks: 执行关闭的回调
事件循环的每个阶段都有一个回调队列,当事件循环进入某个阶段时,会先检查该阶段的回调队列,然后依次执行队列中的回调函数。
微任务的执行时机不一样,Node.js 会在每个阶段之间执行微任务,而浏览器会在每个宏任务后执行微任务。
以下是 AI 的回答。
JavaScript 事件循环与 Node.js 事件循环的区别
浏览器中的事件循环
主要特点
- 单线程:JavaScript 是单线程的,一次只能执行一个任务
- 任务队列:使用任务队列(Task Queue)和微任务队列(Microtask Queue)
- 渲染时机:每次事件循环后会检查是否需要渲染页面
执行顺序
- 执行同步代码
- 执行所有微任务(Microtasks)
Promise.then/catch/finallyMutationObserverqueueMicrotask()
- 执行一个宏任务(Macrotask)
setTimeout/setIntervalsetImmediate(非标准,仅限部分浏览器)- I/O 操作
- UI 渲染
- 重复步骤 2-3
Node.js 中的事件循环
主要阶段
Node.js 使用 libuv 实现的事件循环包含以下阶段(按顺序执行):
- timers 阶段:执行
setTimeout和setInterval的回调 - I/O callbacks 阶段:执行几乎所有的回调(除了 close callbacks、timers、
setImmediate) - idle, prepare 阶段:仅内部使用
- poll 阶段:检索新的 I/O 事件
- 如果 poll 队列不为空,会执行队列中的回调直到队列为空
- 如果 poll 队列为空:
- 如果有
setImmediate回调,进入 check 阶段 - 否则等待新的回调添加到队列中
- 如果有
- check 阶段:执行
setImmediate的回调 - close callbacks 阶段:一些关闭的回调函数(如
socket.on('close', ...))
微任务执行时机
- 在 Node.js 中,微任务队列在事件循环的每个阶段之间执行
- 包括
process.nextTick()和Promise回调
主要区别
| 特性 | 浏览器 | Node.js |
|---|---|---|
| 实现 | 由浏览器实现 | 由 libuv 库实现 |
| 阶段 | 简单(宏任务、微任务) | 6 个明确阶段 |
| 微任务执行时机 | 每个宏任务后 | 每个阶段之间 |
process.nextTick | 不存在 | 在每个阶段结束后立即执行,优先于微任务 |
setImmediate | 非标准 | 在 check 阶段执行 |
| I/O 操作 | 通常由 Web API 处理 | 通过 libuv 的线程池处理 |
| 渲染 | 每次事件循环后检查 | 不涉及渲染 |
代码示例
// 在浏览器和Node.js中执行结果可能不同
console.log("1");
setTimeout(() => console.log("2"), 0);
Promise.resolve().then(() => console.log("3"));
process.nextTick(() => console.log("4")); // 仅Node.js
setImmediate(() => console.log("5")); // 仅Node.js
console.log("6");
浏览器输出(忽略 Node.js 特 有 API):
1
6
3
2
Node.js 输出:
1
6
4
3
2
5
总结
- 执行顺序:Node.js 的事件循环比浏览器更复杂,有明确的阶段划分
- 微任务:在 Node.js 中,微任务在事件循环的每个阶段之间执行,而浏览器中是在每个宏任务后执行
- 特有 API:Node.js 有
process.nextTick和setImmediate等特有 API - I/O 处理:Node.js 使用线程池处理 I/O 操作,而浏览器中通常由浏览器 API 处理
理解这些差异对于编写跨环境的 JavaScript 代码非常重要。