Web 平台 API / 事件循环 · 任务、microtask,与那一圈的所有时机 / 一圈是怎么转的:同步 → microtask → 渲染 → 下一个 macrotask 待审核 1 / 6
the loop · 调用栈 + macrotask + microtask

一圈是怎么转的:同步 → microtask → 渲染 → 下一个 macrotask

JavaScript 是单线程的:同一时刻只有一段代码在跑。那「同时」处理点击、定时器、网络回包靠什么?靠一个持续运转的循环——事件循环 (event loop)。它的规则可以概括为一句:取一个 macrotask 执行完 → 把 microtask 全部清空 →(到时机就渲染)→ 再取下一个 macrotask。本页把这套规则做成一个可单步执行的模拟器,直观说明「为什么同一段同步代码里排下的 Promise.then 先于 setTimeout(fn, 0) 执行」。

注 · 整段 <script> 也是「第一个 macrotask」:它同步跑完,期间排下的 setTimeoutmacrotask 队列Promise.then / queueMicrotaskmicrotask 队列

图 1 · 事件循环模拟器。可单步前进 / 后退,观察整段 script 作为第一个 macrotask 执行、setTimeout 进 macrotask 队列、Promise.then 与 queueMicrotask 进 microtask 队列;四个预设分别是经典顺序题、microtask 中再生 microtask、两个定时器各带 microtask、加入 rAF。

注 · microtask 队列「一次清空到底」——清空过程中新产生的 microtask 也算这一轮,要一并清完。而 macrotask「一次只取一个」,取出一个后就先清空 microtask。因此大量 microtask 会把渲染和下一个 macrotask无限期推后(这一特性在microtask页详述)。

警示 · 渲染并非每一圈都发生。渲染机会跟随显示器刷新率——60Hz 下约每 16.7ms 一次,120Hz 下约 8.3ms 一次;两次机会之间可能已执行了多个 macrotask。本模拟器为说明顺序做了简化:把每个 macrotask 边界都画成一次渲染机会。真实浏览器会合并 / 跳过渲染,rAF 与 0ms 定时器谁先并不保证(取决于当前是否正好到了渲染时机)。