【JavaScript】实现一个高精度的定时器
JS 自带的 setTimeout
/ setInterval
定时器精度有限,常常出现 延迟漂移(drift),尤其在页面被挂起、CPU 忙碌时。
❌ 为什么默认定时器不精确?
- 事件循环:
setTimeout(fn, delay)
的回调要等 主线程空闲 才能执行,可能被阻塞。 - 嵌套定时器最小延迟:Chrome、Firefox 等浏览器在后台或非激活标签页中,
setTimeout
最小延迟可能变为 1000ms。 - 累积误差:
setInterval
是按照“上一次回调开始”计算的,而不是“理想时间点”。执行时间一长,误差会 逐渐累积。
✅ 高精度定时器实现思路
🔹 普通 setInterval 的问题
setInterval(() => {console.log(performance.now());
}, 100);
问题在于:
- JS 是单线程,假设回调耗时 20ms,那么下一个 100ms 的周期会推迟 20ms。
- 误差会 累积:100 → 120 → 140 → …,时间漂移越来越严重。
核心思路:
- 用
performance.now()
(微秒级,远比 Date.now() 好)。 - 不依赖
setInterval
,而是递归使用setTimeout
或requestAnimationFrame
。 - 每次 tick 都以「理想时钟」为基准,而不是以上次实际执行时间为准。这样就算某一次任务被阻塞了(比如长任务或 GC),也能 自我修正,误差不会累积。
- 通过“理想执行时间”与“实际执行时间”的差值来做 纠偏(drift correction)。
- 👉 本质:将 interval 看成理想时钟,而不是依赖 JS 事件循环。
📌 示例:高精度定时器(纠偏版)
function highPrecisionTimer(callback, interval) {let expected = performance.now() + interval;let timerId;function step() {const drift = performance.now() - expected; // 实际时间 - 理想时间callback();// 更新理想的下一次触发点expected += interval;// 纠正漂移:如果 drift > interval,说明任务卡了,就补偿timerId = setTimeout(step, Math.max(0, interval - drift));}timerId = setTimeout(step, interval);return () => clearTimeout(timerId); // 返回清除函数
}// 使用:每 100ms 执行一次
const stop = highPrecisionTimer(() => {console.log("tick", performance.now());
}, 100);// 5 秒后停止
setTimeout(stop, 5000);
👉 特点:
- 误差不会无限累积(比
setInterval
精准得多)。 - 即使执行耗时,也会自动调整到理想时钟。
📌 示例:用 requestAnimationFrame
实现(适合动画)
requestAnimationFrame(callback) 会在 下一帧渲染前 执行,浏览器大多数显示器刷新率是 60Hz(大约 16.6ms 一帧)。
所以:
function loop(now) {console.log(now); // rAF 自带高精度时间戳requestAnimationFrame(loop);
}
requestAnimationFrame(loop);
输出时间间隔大约是 16.6ms,和屏幕刷新强绑定。
function highPrecisionRAF(callback, interval) {let start = performance.now();let lastTime = start;function loop(now) {if (now - lastTime >= interval) {lastTime += interval;callback();}requestAnimationFrame(loop);}requestAnimationFrame(loop);
}// 使用:每 100ms 执行一次
highPrecisionRAF(() => {console.log("tick", performance.now());
}, 100);
👉 特点:
- 绑定浏览器渲染帧(大约 16.6ms 一次)。
- 更适合动画相关的定时器(不会掉帧)。
- 内置高精度 DOMHighResTimeStamp(和 performance.now() 一样)。和屏幕刷新率同步(比如 60Hz),避免动画卡顿。
- 👉 本质:把任务挂载在浏览器渲染周期上,获得更平滑的定时效果。
📌 Node.js 环境高精度定时器
Node.js 可以用 setTimeout
+ process.hrtime.bigint()
(纳秒级),同样做 drift correction。
✅ 总结
setInterval
:误差会累积,不推荐。setTimeout + 纠偏
:高精度,适合定时任务。requestAnimationFrame
:渲染友好,适合动画。Web Worker + setTimeout
:适合后台计算,避免主线程阻塞。
要不要我给你写一个 支持暂停 / 恢复 / 停止 的高精度定时器类?这样能直接用在项目里,管理定时任务更方便。