JavaScript 事件循环深入

在事件循环基础上对照浏览器与 Node 的差异、常见面试输出题模式与工程排障要点。

#type / synthesis #status / growing #tech / dev #resource / javascript #resource / nodejs

[!info] 关联笔记

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 事件循环阶段

面试输出题模式

  1. 先写同步
  2. 登记 timeout/Promise
  3. 同步结束排空 then
  4. 再 timeout
  5. 若有 async 函数,把它拆成“同步前半 + await 后微任务”

工程排障

现象方向
UI 卡死同步长循环/重计算
状态“晚一拍”微任务与渲染时序
Node 服务吞吐怪nextTick 饿死、同步 fs
测试偶发真实异步未 await

本节总结

深入事件循环的目标不是背相位名,而是能预测顺序、能解释卡顿、能选对宿主 API

自测题

  1. 为什么不应在 Node 里用 process.nextTick 递归刷屏?
参考答案

nextTick 队列会优先清空,递归登记会推迟 I/O 与其它相位,导致饿死。

创建于 2025/1/1 更新于 2026/7/15