一、核心区别一句话总结
👉 浏览器:宏任务 + 微任务(简单模型)
👉 Node.js:6 个阶段(phases)+ 两种微任务队列(更复杂)
二、浏览器事件循环(简单模型)
执行流程
- 执行一个宏任务(script / setTimeout / I/O)
- 执行所有微任务(Promise.then / MutationObserver)
- 渲染(如果需要)
- 进入下一个循环
示例
console.log('1');
setTimeout(() => console.log('2'), 0);
Promise.resolve().then(() => console.log('3'));
console.log('4');
👉 输出:
1
4
3
2
关键点
- 微任务优先级高于宏任务
- 每个宏任务执行完,都会清空微任务队列
三、Node.js 事件循环(重点)
Node.js 不是简单的“宏任务/微任务”,而是 6 个阶段(phases)循环执行
Node.js 六大阶段
顺序如下:
1. timers(setTimeout / setInterval)
2. pending callbacks
3. idle, prepare(内部使用)
4. poll(I/O 核心阶段)
5. check(setImmediate)
6. close callbacks
👉 每个阶段都有自己的队列
可视化理解(流程)
timers → pending → poll → check → close
↑ ↓
(循环继续)
四、Node.js 的“微任务”更特殊
Node.js 有 两个微任务队列:
1️⃣ process.nextTick(最高优先级)
2️⃣ Promise.then(普通微任务)
执行优先级
当前阶段执行完
↓
先执行 nextTick 队列
↓
再执行 Promise 微任务
↓
进入下一个阶段
示例(Node 面试高频)
setTimeout(() => console.log('timeout'));
setImmediate(() => console.log('immediate'));
process.nextTick(() => console.log('nextTick'));
Promise.resolve().then(() => console.log('promise'));
👉 输出(Node):
nextTick
promise
timeout / immediate(不确定)
五、Node vs 浏览器 对比(重点总结)
| 对比点 | 浏览器 | Node.js |
|---|---|---|
| 结构 | 宏任务 + 微任务 | 6 个阶段 |
| 微任务类型 | Promise | nextTick + Promise |
| 微任务执行时机 | 宏任务后 | 每个阶段后 |
| 优先级最高 | Promise | process.nextTick |
| setImmediate | ❌ 没有 | ✅ 有 |
| I/O处理 | 简单 | poll阶段(核心) |
六、最容易踩坑的区别(很重要)
1️⃣ nextTick 会“饿死”事件循环
function loop() {
process.nextTick(loop);
}
loop();
👉 会导致:
- I/O 永远不执行
- 事件循环卡死
⚠️ 浏览器没有这个问题
2️⃣ setTimeout vs setImmediate(Node)
setTimeout(() => console.log('timeout'), 0);
setImmediate(() => console.log('immediate'));
👉 在不同环境下顺序可能不同:
- 主线程:不确定
- I/O回调中:setImmediate 更快
七、一句话面试总结(可以直接背)
👉 浏览器事件循环是“宏任务 + 微任务”的简单模型,而 Node.js 是基于 libuv 的多阶段事件循环(timers、poll、check 等),并且 Node 中存在两个微任务队列(process.nextTick 和 Promise),其中 nextTick 优先级最高。