事件循环

建立浏览器事件循环的任务队列模型,解释同步代码、微任务与宏任务的交错顺序及工程含义。

#type / concept #status / growing #tech / dev #resource / javascript

[!info] 关联笔记

事件循环

这个概念为什么出现

JS 在浏览器主线程里跑 UI 与脚本。若同步死循环,页面冻结;若异步回调乱序理解,会出现:

  • setTimeoutPromise.then 却预测反了
  • 在 effect/渲染后读到的状态时序错乱
  • 把 sleep 当锁

事件循环解释:单线程如何并发地处理 I/O 与渲染相关任务

[!abstract] 一句话理解 调用栈清空后,先尽可能清空微任务,再取下一个宏任务;渲染机会插在任务之间(浏览器实现细节有弹性)。

最小可运行示例

场景:下单按钮日志顺序排障

// 业务:点击下单后埋点与请求回调交错
console.log('1 同步:校验购物车')

setTimeout(() => {
  console.log('4 宏任务:timeout 回调')
}, 0)

Promise.resolve().then(() => {
  console.log('3 微任务:promise then')
})

console.log('2 同步:发起请求前')

建议运行:

node order-loop.js
# 或浏览器控制台

期望输出(确定):

1 同步:校验购物车
2 同步:发起请求前
3 微任务:promise then
4 宏任务:timeout 回调

结合场景关注点

  1. 同步永远先跑完当前栈。
  2. then 微任务插队在 timeout 前。
  3. 0ms 不是“立即”,是“尽快作为定时器任务”。

核心模型

flowchart TD
  A[执行同步脚本/一个任务] --> B[清空微任务队列]
  B --> C[可能渲染/更新]
  C --> D[取下一个宏任务]
  D --> A

宏任务 vs 微任务(浏览器教学)

类型例子
宏任务setTimeout setInterval 部分 I/O/事件回调
微任务Promise.then queueMicrotask MutationObserver

规范/宿主:HTML 的 event loop 与 ECMAScript jobs 相互衔接;Node 另有相位模型,见 Node 事件循环阶段。不要把 Node 相位硬套浏览器。

设计动机

用队列把“等待中的工作”从调用栈拆走,保持单线程可预测,同时让 I/O 完成通知稍后继续。

边界

  • requestAnimationFrame 与渲染节奏相关,不简单等同 timeout。
  • 微任务过多会推迟渲染,造成卡顿。
  • 竞态应用 AbortController/序号,而不是靠“延时更长”。

常见误区

[!warning] 常见误区:async/await 创造多线程 await 把后续变成 Promise 微任务链,仍在同一线程事件循环上。

工程实践

  • 长计算切片或 Web Worker。
  • 不要用 setTimeout 当同步锁。
  • 预测输出题先画“同步 → 微任务 → 宏任务”。

本节总结

事件循环是浏览器 JS 并发的时间表。会排序,才会写稳定异步与可解释的 UI 更新。

自测题

  1. 为什么大量同步 then 链会卡渲染?
  2. await 后的代码大概何时执行?
参考答案
  1. 微任务在渲染前被优先排空,过长会推迟绘制。
  2. 当前 await 的 Promise 兑现后,作为微任务续跑(在对应任务点)。

延伸阅读

资料类型支撑内容
HTML event loops规范浏览器循环
MDN Event loop文档模型
创建于 2025/1/1 更新于 2026/7/15