setTimeout最小延迟是规范强制的节流机制:嵌套超5层且delay<4ms时升至≥4ms,前5层可低至0.3–1.2ms;非活跃标签页延迟升至1000ms或10000ms;应优先选用requestAnimationFrame、queueMicrotask等更匹配的API。

setTimeout 的最小延迟不是“大约 4ms”,而是一套有明确触发条件的节流机制:只有当定时器嵌套层级超过 5(即第 6 层起),且传入的 delay 小于 4ms 时,浏览器才会强制将其提升至 ≥4ms。前 5 层仍可能低至 0.3–1.2ms;非活跃标签页下,该限制会进一步恶化至 1000ms 或 10000ms。
嵌套层级怎么算?不是缩进,是宏任务链深度
层级按 setTimeout 触发的宏任务调用路径累计,和函数是否嵌套、有没有中间插 Promise.then 无关。只要 A 调用 setTimeout → B 执行后又调用 setTimeout → C 再调用……这样连续的 setTimeout 调用,每调一次就加一层。第 6 次调用即触发 4ms 强制节流。
- 例如:
setTimeout(f1,0); f1→setTimeout(f2,0); f2→setTimeout(f3,0); …… 到 f6 时,实际延迟跳变 - 中间穿插
Promise.resolve().then()或queueMicrotask不重置计数 -
postMessage + message模拟也不绕得过——现代浏览器已将常见旁路纳入统一节流
为什么是第 6 层?标准防的是无限调度
HTML Living Standard 明确规定:“If nesting level is greater than 5, and timeout is less than 4, then set timeout to 4”。本质是防止脚本通过连续 setTimeout(0) 构造伪无限循环,持续抢占主线程,导致页面卡死或设备过热。
- 前 5 层留作缓冲,允许轻量级异步脱钩(如状态更新、防阻塞)
- 第 6 层起视为“高风险调度模式”,浏览器主动降频保稳定
- 这不是 bug,也不是性能缺陷,而是规范强制行为,Chrome、Firefox、Edge 均严格遵守
非活跃标签页:4ms 可能变成 1 秒甚至 10 秒
用户切走当前页后,浏览器会大幅放宽定时器精度以省电,这与嵌套层级无关,但常被误认为是“嵌套更深导致的延迟飙升”。
- Chrome / Firefox 桌面版:后台标签中
setTimeout(0)实际延迟 ≥1000ms - Firefox 开启脚本追踪后:延迟直接升至 ≥10000ms(10 秒),且从文档加载完成 30 秒后开始生效
- 唯一可靠绕过方式:页面播放 Web Audio(需显式调用
audioContext.resume())
别硬刚 4ms,换工具更有效
试图“欺骗”浏览器层级计数没有意义。真正需要高频或精准调度的场景,应选用语义更匹配的原生 API:
- 动画同步:用
requestAnimationFrame,它在下一帧重绘前执行,不参与宏任务排队 - 微任务脱钩:用
queueMicrotask,不计入嵌套层级,无 4ms 限制,开销更低 - 音频/采样级控制:Web Audio API +
audioContext.currentTime,精度达亚毫秒 - Worker 中密集计算:
setTimeout同样受 4ms 限制,无法靠“挪到 Worker”提精度


















