🚀 浏览器 vs Node:事件循环
- 浏览器的事件循环 = 多线程 + 微任务在每个宏任务后执行 + 渲染时机;
- Node 的事件循环 = libuv 六阶段模型 + nextTick 优先级更高 + setImmediate 特殊阶段。
🧠 microtask
- micro = 微、小、紧接、立即
- task = 任务
组合起来就是:microtask:比普通任务(宏任务)更快执行、更高优先级的小任务。
常见 microtask:
Promise.thenqueueMicrotask- (浏览器特有)
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 驱动,事件循环分“阶段”执行:
- timers — setTimeout / setInterval
- pending callbacks
- idle / prepare
- poll — 处理 I/O
- check — setImmediate
- 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 关注“系统调度”。