Node.js与浏览器事件循环目标一致但机制不同:浏览器为渲染驱动,采用宏任务→微任务→渲染三步模型;Node.js由libuv实现六阶段I/O流水线,且微任务在每阶段后执行,并区分process.nextTick与Promise优先级。

Node.js 和浏览器的事件循环,核心目标一致——让单线程 JavaScript 能非阻塞地处理异步任务,但实现逻辑和调度节奏完全不同。不能简单套用“宏任务→微任务”这一套去理解 Node.js 的行为。
浏览器:渲染驱动的两队列模型
浏览器事件循环围绕 UI 响应设计。每轮循环大致走三步:
执行一个宏任务(比如 script、setTimeout 回调)→ 立即清空全部微任务(Promise.then、queueMicrotask)→ 触发页面渲染(如果需要)。
关键点在于:
• 渲染是显式环节,浏览器必须留出时间给样式计算、布局、绘制;
• 微任务只在宏任务结束后集中执行一次,不会跨轮次累积;
• requestAnimationFrame 与渲染同步,是浏览器特有机制。
Node.js:I/O 驱动的六阶段流水线
Node.js 不处理 UI,所以没有渲染阶段,而是由 libuv 实现一套更精细的分阶段调度:
立即学习“Java免费学习笔记(深入)”;
- timers:执行 setTimeout/setInterval 到期回调
- pending callbacks:处理系统级 I/O 回调(如 TCP 错误)
- poll:最核心阶段,获取并执行 I/O 回调;若无任务且有 setImmediate,则可能提前退出
- check:执行 setImmediate() 回调
- close callbacks:触发 socket.close 等关闭事件
每个阶段执行完,都会先跑完 process.nextTick 队列,再跑 Promise 微任务 队列——这是 Node.js 特有的两级微任务优先级。
微任务执行时机根本不同
这是最容易踩坑的地方:
- 浏览器:微任务只在宏任务后执行一次,不管当前处于哪个“逻辑轮次”
- Node.js:微任务插在每个阶段之间。比如 timers 阶段执行完 → 执行 nextTick + Promise → 进入 pending callbacks 阶段 → 再执行 nextTick + Promise → 继续下一阶段
- 这意味着,在 Node.js 中连续写多个 process.nextTick,会打断整个阶段流转,甚至阻塞 setImmediate 或 I/O 回调
API 行为差异直接体现机制区别
同一段代码,在两个环境输出顺序可能不同:
-
setTimeout(fn, 0)和setImmediate(fn)在 Node.js 中不等价:前者在 timers 阶段,后者在 check 阶段;若 timers 阶段很快完成且 poll 阶段无任务,setImmediate 可能先于 setTimeout 执行 -
process.nextTick()是 Node.js 独有,优先级高于所有 Promise 微任务,且仅在当前操作结束后立即执行,不属于标准 Web API - 浏览器没有 setImmediate、process.nextTick,也没有 poll 阶段概念;Node.js 也没有 requestAnimationFrame


















