JS 自带的 setTimeout / setInterval 定时器精度有限,常常出现 延迟漂移(drift),尤其在页面被挂起、CPU 忙碌时。


❌ 为什么默认定时器不精确?

  • 事件循环setTimeout(fn, delay) 的回调要等 主线程空闲 才能执行,可能被阻塞。
  • 嵌套定时器最小延迟:Chrome、Firefox 等浏览器在后台或非激活标签页中,setTimeout 最小延迟可能变为 1000ms。
  • 累积误差setInterval 是按照“上一次回调开始”计算的,而不是“理想时间点”。执行时间一长,误差会 逐渐累积

✅ 高精度定时器实现思路

🔹 普通 setInterval 的问题

setInterval(() => {
  console.log(performance.now());
}, 100);

问题在于:

  1. JS 是单线程,假设回调耗时 20ms,那么下一个 100ms 的周期会推迟 20ms。
  2. 误差会 累积:100 → 120 → 140 → …,时间漂移越来越严重。

核心思路:

  1. performance.now()(微秒级,远比 Date.now() 好)。
  2. 不依赖 setInterval,而是递归使用 setTimeoutrequestAnimationFrame
  3. 每次 tick 都以「理想时钟」为基准,而不是以上次实际执行时间为准。这样就算某一次任务被阻塞了(比如长任务或 GC),也能 自我修正,误差不会累积。
  4. 通过“理想执行时间”与“实际执行时间”的差值来做 纠偏(drift correction)
  5. 👉 本质:将 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:适合后台计算,避免主线程阻塞。

要不要我给你写一个 支持暂停 / 恢复 / 停止 的高精度定时器类?这样能直接用在项目里,管理定时任务更方便。

更多推荐