Skip to main content

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)
  • 渲染时机:每次事件循环后会检查是否需要渲染页面

执行顺序

  1. 执行同步代码
  2. 执行所有微任务(Microtasks)
    • Promise.then/catch/finally
    • MutationObserver
    • queueMicrotask()
  3. 执行一个宏任务(Macrotask)
    • setTimeout/setInterval
    • setImmediate(非标准,仅限部分浏览器)
    • I/O 操作
    • UI 渲染
  4. 重复步骤 2-3

Node.js 中的事件循环

主要阶段

Node.js 使用 libuv 实现的事件循环包含以下阶段(按顺序执行):

  1. timers 阶段:执行 setTimeoutsetInterval 的回调
  2. I/O callbacks 阶段:执行几乎所有的回调(除了 close callbacks、timers、setImmediate
  3. idle, prepare 阶段:仅内部使用
  4. poll 阶段:检索新的 I/O 事件
    • 如果 poll 队列不为空,会执行队列中的回调直到队列为空
    • 如果 poll 队列为空:
      • 如果有 setImmediate 回调,进入 check 阶段
      • 否则等待新的回调添加到队列中
  5. check 阶段:执行 setImmediate 的回调
  6. 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

总结

  1. 执行顺序:Node.js 的事件循环比浏览器更复杂,有明确的阶段划分
  2. 微任务:在 Node.js 中,微任务在事件循环的每个阶段之间执行,而浏览器中是在每个宏任务后执行
  3. 特有 API:Node.js 有 process.nextTicksetImmediate 等特有 API
  4. I/O 处理:Node.js 使用线程池处理 I/O 操作,而浏览器中通常由浏览器 API 处理

理解这些差异对于编写跨环境的 JavaScript 代码非常重要。