浏览器event loop是“宏任务→微任务→渲染”单线性循环,node.js基于libuv分六阶段轮转;前者每宏任务后必清空微任务并渲染,后者各阶段内执行对应回调、微任务仅在阶段切换前检查,且无渲染环节。

浏览器和 Node.js 都用 Event Loop 处理异步,但底层结构、任务调度逻辑和执行节奏完全不同——不是“同一套机制跑在不同地方”,而是两套独立设计的调度系统,服务于各自的核心目标:浏览器保 UI 流畅,Node.js 保 I/O 高效。
结构上:单线性链条 vs 六阶段轮转
浏览器 Event Loop 是固定四步循环:执行一个宏任务 → 清空全部微任务 → 触发渲染 → 进入下一轮。每轮只取一个宏任务,之后必须把所有已排队微任务(包括新 Promise 产生的嵌套微任务)执行完,再走渲染。
Node.js 基于 libuv,划分为六个明确阶段:timers → pending callbacks → poll → check → close callbacks。每个阶段只处理自己队列里的回调,比如:
- poll 阶段专注 I/O 完成回调(如 fs.readFile),不处理 setTimeout 或 setImmediate
- check 阶段才执行 setImmediate 回调,一定在 poll 之后、close 之前
- timers 阶段才真正执行 setTimeout/setInterval,哪怕设为 0,也可能被 poll 阶段阻塞几毫秒
微任务执行时机与清空逻辑不同
浏览器中,只要同步代码结束,就立即、递归式清空当前所有微任务——Promise 链会一口气跑到底,输出顺序确定。
Node.js 中,微任务只在阶段切换前检查并执行,且行为随版本变化:
- v10 及以前:I/O 回调结束后清空全部微任务(接近浏览器)
- v11 起:只执行当前可运行的微任务,新产生的可能卡到下一阶段(如 check 或 timers)才执行
- v12+ 恢复部分清空语义,但高密度 I/O 仍可能导致 Promise.then 被截断
例如:Promise.resolve().then(() => console.log(1)).then(() => console.log(2)) 在 fs 回调里,Node.js 可能只输出 1,2 要等到下一轮 check 阶段才执行。
独有调度工具:nextTick 和 setImmediate
这两个 API 浏览器没有,却是 Node.js 精细控制时序的关键:
- process.nextTick 不是微任务,也不是宏任务;它在每个阶段即将退出时强制插入,优先级高于 Promise.then,甚至高于当前阶段剩余任务
- setImmediate 属于 check 阶段,在 poll 阶段结束后立即执行,因此比同为 0 毫秒的 setTimeout() 更早触发(后者在 timers 阶段)
- 这导致一段含 setTimeout、setImmediate、nextTick 的代码,在 Node.js 中执行顺序稳定为:nextTick → setImmediate → setTimeout,而浏览器根本无法运行后两者
渲染环节:有无决定行为边界
浏览器 Event Loop 显式包含“渲染”步骤,且微任务总在渲染前执行,保证 DOM 更新与视图一致。比如 MutationObserver 和 Promise.then 都能确保在重绘前完成 DOM 修改。
Node.js 完全没有渲染概念,也不存在“渲染前/后”的语义。它的事件循环只为调度 I/O、定时器、关闭等系统级操作服务,所有任务都围绕资源调度效率展开。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











