JavaScript定时器本质是“最小延迟触发器”而非精确计时工具,其执行受事件循环、浏览器节流(如4ms下限、后台标签页1000ms限制)及系统硬件(CPU降频、GC暂停、系统时钟跳变)多重影响;应通过performance.now()动态校准、requestAnimationFrame、Web Worker或服务端时间戳等方式控制偏差。

JavaScript 定时器不是不准,而是它根本就不是为“精确计时”设计的。它的核心定位是“最小延迟触发器”——只保证回调不会早于设定时间执行,但实际触发时刻由运行环境层层决定。
事件循环才是真正的调度者
setTimeout 和 setInterval 的回调属于宏任务,必须排队等待:
- 当前调用栈完全清空(同步代码执行完)
- 所有微任务(Promise.then、queueMicrotask 等)执行完毕
- 浏览器完成渲染(可选,取决于是否在帧末尾)
- 事件循环从宏任务队列中取出该定时器回调
哪怕你设了 setTimeout(fn, 1),只要主线程正执行一个 50ms 的 DOM 更新,fn 就得等到 51ms 之后才可能开始执行。
浏览器主动施加的硬性节流
为节省资源、延长电池寿命,浏览器在多种场景下会强制拉低定时器分辨率:
立即学习“Java免费学习笔记(深入)”;
- 4ms 下限规则:Chrome/Firefox 对连续嵌套 5 层以上的 setTimeout,自动将 delay 提升至 ≥4ms;Edge 更激进,第 3 层即触发
- 后台标签页节流:页面切到后台时,setInterval 最小间隔被拉高至 1000ms;部分安卓版 Firefox 甚至延长至 15 分钟
- 跟踪脚本降频:Firefox 55+ 识别出 GA 等分析脚本后,额外延长其定时器延迟
系统与硬件层的真实拖累
即使 JS 代码完全空闲,精度仍受底层不可控因素影响:
- CPU 动态降频(如笔记本节能模式)导致 performance.now() 增量不均匀
- 垃圾回收暂停、内存页错误、缓存未命中可引入毫秒级抖动
- 操作系统以时间片调度线程,JS 无法保证在指定时刻获得 CPU 时间
- 系统时钟本身可能被 NTP 校正跳变,Date.now() 并非稳定单调源
比“修精度”更重要的是“控偏差”
与其纠结为什么不准,不如接受它,并让逻辑对误差免疫:
- 用 performance.now() 记录每次真实执行时刻,动态调整下次 delay(例如本次晚了 7ms,下次 delay = 目标值 − 7)
- 动画类任务改用 requestAnimationFrame,它绑定屏幕刷新节奏,自动暂停不可见页签
- 倒计时、心跳检测等时间敏感逻辑,移入 Web Worker 独立运行,避免主线程阻塞干扰
- 全局一致时间场景(如协作编辑),以服务端时间戳为基准,前端仅做插值渲染


















