浏览器和 Node.js 的事件循环有什么区别?
浏览器事件循环
同步代码 → 微任务全部执行 → 一个宏任务 → 微任务全部执行 → 一个宏任务 → ...微任务:Promise.then、queueMicrotask、MutationObserver
宏任务:setTimeout、setInterval、I/O、UI 渲染
Node.js 事件循环
┌───────────────────────────┐
┌─>│ timers │ ← setTimeout/setInterval
│ └─────────────┬─────────────┘
│ ┌─────────────┴─────────────┐
│ │ pending callbacks │ ← 系统级回调
│ └─────────────┬─────────────┘
│ ┌─────────────┴─────────────┐
│ │ idle, prepare │ ← 内部使用
│ └─────────────┬─────────────┘
│ ┌─────────────┴─────────────┐
│ │ poll │ ← I/O 事件
│ └─────────────┬─────────────┘
│ ┌─────────────┴─────────────┐
│ │ check │ ← setImmediate
│ └─────────────┬─────────────┘
│ ┌─────────────┴─────────────┐
│ │ close callbacks │ ← close 事件
│ └───────────────────────────┘
│
└── 每个阶段之间执行微任务关键区别
| 特性 | 浏览器 | Node.js |
|---|---|---|
| 微任务执行时机 | 每个宏任务后 | 每个阶段切换时 |
| process.nextTick | 无 | 有,优先级最高 |
| setImmediate | 无 | 有,check 阶段执行 |
| 微任务类型 | Promise.then 等 | + process.nextTick |
经典面试题:setTimeout vs setImmediate
// 顺序不确定(取决于事件循环启动时间)
setTimeout(() => console.log('timeout'), 0)
setImmediate(() => console.log('immediate'))
// 但在 I/O 回调中,setImmediate 一定先于 setTimeout
fs.readFile('file.txt', () => {
setTimeout(() => console.log('timeout'), 0)
setImmediate(() => console.log('immediate'))
// 总是输出: immediate → timeout
})process.nextTick
Promise.resolve().then(() => console.log('promise'))
process.nextTick(() => console.log('nextTick'))
// 输出: nextTick → promise
// nextTick 优先级高于所有微任务Node.js 11+ 的变化
Node.js 11 开始,浏览器和 Node.js 的微任务执行时机趋于一致:每个宏任务后立即执行微任务,而非等到当前阶段所有宏任务都执行完。