一圈是怎么转的:同步 → microtask → 渲染 → 下一个 macrotask
JavaScript 是单线程的:同一时刻只有一段代码在跑。那「同时」处理点击、定时器、网络回包靠什么?靠一个持续运转的循环——事件循环 (event loop)。它的规则可以概括为一句:取一个 macrotask 执行完 → 把 microtask 全部清空 →(到时机就渲染)→ 再取下一个 macrotask。本页把这套规则做成一个可单步执行的模拟器,直观说明「为什么同一段同步代码里排下的 Promise.then 先于 setTimeout(fn, 0) 执行」。
注 · 整段 <script> 也是「第一个 macrotask」:它同步跑完,期间排下的 setTimeout 进macrotask 队列、Promise.then / queueMicrotask 进microtask 队列。
注 · microtask 队列「一次清空到底」——清空过程中新产生的 microtask 也算这一轮,要一并清完。而 macrotask「一次只取一个」,取出一个后就先清空 microtask。因此大量 microtask 会把渲染和下一个 macrotask无限期推后(这一特性在microtask页详述)。
警示 · 渲染并非每一圈都发生。渲染机会跟随显示器刷新率——60Hz 下约每 16.7ms 一次,120Hz 下约 8.3ms 一次;两次机会之间可能已执行了多个 macrotask。本模拟器为说明顺序做了简化:把每个 macrotask 边界都画成一次渲染机会。真实浏览器会合并 / 跳过渲染,rAF 与 0ms 定时器谁先并不保证(取决于当前是否正好到了渲染时机)。