JavaScript 事件循环深入
在事件循环基础上对照浏览器与 Node 的差异、常见面试输出题模式与工程排障要点。
#type / synthesis
#status / growing
#tech / dev
#resource / javascript
#resource / nodejs
[!info] 关联笔记
- 前置:事件循环 · Promise
- 宿主:浏览器 JS MOC · Node.js 侧 JS MOC
- 相关:Node 相位 · 定时器
JavaScript 事件循环深入
这个概念为什么出现
只记住“微任务先于宏任务”还不够:
- Node 有 timers/poll/check 等相位
- 浏览器还要插入渲染
process.nextTick与 Promise 微任务不同档
本篇把对照与排障模式补全。
[!abstract] 一句话理解 浏览器与 Node 共享“任务队列 + 单线程执行”直觉,但调度分区与额外队列不同;写跨端代码时不要共用一张简化图硬套。
最小对照示例
场景:同一段日志在浏览器控制台与 Node 都跑
console.log('sync')
setTimeout(() => console.log('timeout'))
Promise.resolve().then(() => console.log('then'))
两边通常都是:sync → then → timeout。差异出现在 setImmediate/nextTick/I/O 相位等 Node 特有 API。
浏览器侧加深
- 一个宏任务结束 → 微任务排空 → 可能 style/layout/paint
- 输入事件、消息、定时器都以任务形式进入循环
- 长任务阻塞输入与帧
Node 侧加深
- 相位:timers、pending、idle/prepare、poll、check、close 等
setImmediate在 check 相位process.nextTick优先级很高,滥用会饿死循环
详见 Node 事件循环阶段。
面试输出题模式
- 先写同步
- 登记 timeout/Promise
- 同步结束排空 then
- 再 timeout
- 若有 async 函数,把它拆成“同步前半 + await 后微任务”
工程排障
| 现象 | 方向 |
|---|---|
| UI 卡死 | 同步长循环/重计算 |
| 状态“晚一拍” | 微任务与渲染时序 |
| Node 服务吞吐怪 | nextTick 饿死、同步 fs |
| 测试偶发 | 真实异步未 await |
本节总结
深入事件循环的目标不是背相位名,而是能预测顺序、能解释卡顿、能选对宿主 API。
自测题
- 为什么不应在 Node 里用
process.nextTick递归刷屏?
参考答案
nextTick 队列会优先清空,递归登记会推迟 I/O 与其它相位,导致饿死。