手写一个高精度倒计时器,分别用 setTimeout 递归 和 requestAnimationFrame 实现,并说明两者差异(参考 PDD 前端笔试题)。

基于 setTimeout 递归(后台仍可计时)

用时间戳计算剩余时间,而不是简单递减,避免定时器漂移:

function countdownByTimeout(duration, onTick, onEnd) {
  const endTime = Date.now() + duration
  function loop() {
    const remain = endTime - Date.now()
    if (remain <= 0) {
      onTick(0)
      onEnd && onEnd()
      return
    }
    onTick(remain)
    // 越接近结束,间隔调小,减少「跳秒」误差
    const delay = remain < 1000 ? 100 : 250
    setTimeout(loop, delay)
  }
  loop()
}

countdownByTimeout(60000, (ms) => {
  console.log((ms / 1000).toFixed(1) + 's')
}, () => console.log('时间到'))

基于 requestAnimationFrame(与渲染帧同步,后台暂停)

function countdownByRAF(duration, onTick, onEnd) {
  const endTime = performance.now() + duration
  function loop(now) {
    const remain = endTime - now
    if (remain <= 0) {
      onTick(0)
      onEnd && onEnd()
      return
    }
    onTick(remain)
    requestAnimationFrame(loop)
  }
  requestAnimationFrame(loop)
}

两者差异

维度 setTimeout 递归 requestAnimationFrame
后台标签页 继续计时 暂停计时
精度 依赖时间戳,误差小 随帧,丝滑
适合 真实倒计时(考试、抢购) 页面 UI 展示倒计时

关键:用 Date.now() / performance.now() 算剩余时间,而不是靠计数器递减——这样即使两次回调间隔不准,显示依然准确。

同分类其他题目