浏览器 vs Node 中的 Event Loop

20 阅读2分钟

🚀 浏览器 vs Node:事件循环

  1. 浏览器的事件循环 = 多线程 + 微任务在每个宏任务后执行 + 渲染时机;
  2. Node 的事件循环 = libuv 六阶段模型 + nextTick 优先级更高 + setImmediate 特殊阶段。

🧠 microtask

  • micro = 微、小、紧接、立即
  • task = 任务

组合起来就是:microtask:比普通任务(宏任务)更快执行、更高优先级的小任务。

常见 microtask:

  • Promise.then
  • queueMicrotask
  • (浏览器特有)MutationObserver
  • (Node 特有)process.nextTick(优先级比所有 microtask 还高)
任务特点
微任务当前宏任务结束的“收尾工作”
宏任务主流程

🌐 浏览器事件循环

浏览器执行一段 JS 的顺序通常是:同步代码 → 微任务(清空)→ 渲染 → 下一个宏任务

典型例子:

console.log('sync 1');

setTimeout(() => console.log('timeout'), 0);

Promise.resolve().then(() => console.log('promise'));

queueMicrotask(() => console.log('queueMicrotask'));

console.log('sync 2');

输出:

sync 1
sync 2
promise
queueMicrotask
timeout

📌 读法:

  • 同步先执行
  • 微任务全部清空
  • 然后才执行下一个宏任务(如 setTimeout)

并且,浏览器有渲染线程,如果微任务很重,会卡 UI(因为渲染被挡住)

🖥 Node 事件循环(重点:六阶段 + 特供 API)

Node 的底层由 libuv 驱动,事件循环分“阶段”执行:

  1. timers — setTimeout / setInterval
  2. pending callbacks
  3. idle / prepare
  4. poll — 处理 I/O
  5. check — setImmediate
  6. close callbacks

关键区别:Node 在每个阶段结束后都会执行微任务队列(Promise),并在其前优先清空 nextTick 队列。

例如:

console.log('sync 1');

setTimeout(() => console.log('timeout'), 0);

Promise.resolve().then(() => console.log('promise'));

process.nextTick(() => console.log('nextTick'));

console.log('sync 2');

输出:

sync 1
sync 2
nextTick
promise
timeout

🔍 Node特有的逻辑:

  • nextTick 优先级最高(比 Promise 更早)
  • pure JS 下 setTimeout 多半比 setImmediate 先执行
  • 在 I/O 回调里顺序可能反过来(面试陷阱)

⚔️ 浏览器 vs Node 的核心区别梳理

对比项浏览器Node
微任务执行时机每个宏任务后执行一次每个阶段结束后执行(更频繁)
process.nextTick❌ 无✔ 最高优先级
setImmediate❌ 无✔ check 阶段专用
渲染机制✔ 有渲染阶段❌ 无渲染
多线程结构✔ JS 主线程 + 网络/计时器/渲染线程✔ JS 单线程 + libuv 线程池
MutationObserver✔ 微任务❌ 没有

浏览器关注“渲染体验”,Node 关注“系统调度”。