JavaScript 倒计时偏差纠正

解决 setTimeout/setInterval 实现倒计时时的时间偏差问题

问题

使用 setTimeoutsetInterval 实现倒计时时会出现时间偏差,如何纠正这个偏差?

解答

偏差产生的原因

JavaScript 是单线程执行的,setTimeoutsetInterval 只是将回调函数加入事件队列,而不是立即执行。当执行栈为空时才会取出队列中的事件执行,这个等待时间就是偏差的来源。

方案一:服务器校准

定时向服务器请求最新时间差,用服务器时间来校准倒计时。

function serverSyncCountdown(endTime) {
  function update() {
    fetch('/api/server-time')
      .then(res => res.json())
      .then(data => {
        const serverTime = data.timestamp;
        const remaining = endTime - serverTime;
        
        if (remaining > 0) {
          displayTime(remaining);
          setTimeout(update, 1000);
        }
      });
  }
  
  update();
}

方案二:自动调整间隔时间

通过计算偏差来动态调整下次定时器的间隔时间。

function accurateCountdown(duration) {
  let startTime = Date.now();
  let count = 0;
  const interval = 1000;
  
  function tick() {
    count++;
    
    // 计算理论上应该经过的时间
    const idealTime = count * interval;
    // 计算实际经过的时间
    const actualTime = Date.now() - startTime;
    // 计算偏差
    const drift = actualTime - idealTime;
    
    const remaining = duration - count;
    
    if (remaining > 0) {
      displayTime(remaining);
      // 用间隔时间减去偏差,纠正下次执行时间
      setTimeout(tick, interval - drift);
    }
  }
  
  tick();
}

function displayTime(seconds) {
  console.log(`剩余时间: ${seconds}秒`);
}

// 使用示例:60秒倒计时
accurateCountdown(60);

关键点

  • setTimeoutsetInterval 的回调不是精确执行,会因为事件队列等待产生偏差
  • 服务器校准方案适合需要多端同步的场景,但会增加网络请求
  • 自动调整间隔方案通过 实际时间 - 理论时间 计算偏差,然后从下次间隔中减去偏差值
  • 使用 Date.now() 获取时间戳比 new Date() 性能更好
  • 递归 setTimeoutsetInterval 更适合实现精确倒计时