手写一个高精度倒计时器,分别用 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()算剩余时间,而不是靠计数器递减——这样即使两次回调间隔不准,显示依然准确。