JavaScript事件循环是单线程下通过微任务优先、宏任务次之的任务调度实现异步,调用栈清空后才执行任务;微任务(如Promise)总在宏任务(如setTimeout)前连续执行,确保可预测的执行顺序。

JavaScript 事件循环是单线程环境下实现“看似并行”异步行为的底层机制。它不靠多线程,而是靠精确的任务调度:同步代码立即执行,异步回调按类型排队(微任务优先、宏任务次之),并在主线程空闲时依次取出执行。
调用栈清空后才启动任务调度
JavaScript 只有一个主线程,所有同步代码都在调用栈中顺序执行。函数调用入栈,执行完出栈。只有当调用栈完全清空,事件循环才会介入——它不会打断正在运行的函数,也不会提前执行回调。
- 同步代码(如 console.log、变量赋值)一定最先输出
- 哪怕 setTimeout 设为 0,其回调也必须等同步代码和所有微任务执行完才能执行
- 长时间运行的同步代码(比如 10 万次循环)会彻底阻塞事件循环,导致页面无响应
微任务总比宏任务先执行
事件循环每轮只取一个宏任务(如 setTimeout 回调、I/O 完成回调),但会在该宏任务执行完毕后,**立刻、连续执行所有当前待处理的微任务**,直到微任务队列为空。
- 微任务包括:Promise.then/catch/finally、queueMicrotask、MutationObserver 回调
- 宏任务包括:setTimeout、setInterval、script 整体、I/O、UI 渲染、requestAnimationFrame
- 这个优先级规则解释了为什么 Promise 总在 setTimeout 前输出,即使延时为 0
理解事件循环直接决定异步逻辑是否可靠
很多看似“理所当然”的行为,其实都依赖事件循环的精确时序。忽略它,就容易写出竞态、卡顿或渲染错位的代码。
立即学习“Java免费学习笔记(深入)”;
- DOM 更新后立即读取节点尺寸?可能读不到——因为渲染是宏任务,而 Promise.then 是微任务,需用 requestAnimationFrame 或 queueMicrotask 对齐时机
- 测试中 mock setTimeout 却断言失败?jest.runAllTimers() 不处理微任务,漏掉 Promise.then 导致状态未更新
- 接口请求后马上操作数据却报错?await fetch() 后的代码是微任务,但 DOM 更新或 Vue 的 nextTick 是另一层调度,需明确等待点
它不是黑箱,而是可预测的执行模型
事件循环没有随机性。只要清楚当前有哪些同步代码、哪些微任务、哪些宏任务,就能准确推导出完整执行顺序。这种可预测性,是调试异步问题、设计状态流转、优化首屏性能的基础。
- 用 console.time() + 多个 Promise.then 可观察微任务链的连续执行
- 把耗时计算拆成多个 setTimeout(0) 分片,就是主动让出主线程给事件循环处理用户交互
- 用 queueMicrotask 替代 setTimeout(0) 实现更及时的状态同步,避免 UI 渲染延迟











