Haydenull

Haydenull

A front-end developer with a passion for using technology to increase personal efficiency and productivity 💡.
twitter

2022-07-03 周报

使用 JavaScript 实现倒计时、JS 定时器注意事项

1. JS 倒计时#

agenda 插件需要增加番茄钟功能,在 github 上找到 usePomodoro ,看起来功能挺全的,而且使用 hook 使用起来很方便,虽然 star 数量并不多,但是这个功能也不复杂,如果有问题我自己能 hold 住,所以最终就选了它。

但是在功能开发完上线后才发现,总是计时不准确,明明设置的 25min 番茄钟,结果一个小时过去了,倒计时也没跑完。

后来拿着手机对比研究一下,确定了当软件处于后台时就会出现误差。突然想起来以前也遇见过类似的情况,当浏览器 tab 处于非激活的状态时,定时器就可能不准确。

去仓库看了下代码,果然问题出在这里:

// 原代码使用 setInterval,来计算倒计时剩余时长
setInterval(tick, 1000)

当 tab 不处于激活状态(tab 被隐藏,浏览器处于后台)时,setInterval 的触发间隔就并不是设定的 1000ms。这可能是浏览器出于性能与耗电等考虑设计的机制。

那么如何处理呢?

既然 js 不能保证代码每隔 1s 执行一次,就只能从外部下手。比较普遍的方法是使用系统时间作为参照物:

function countDown(length) {
  const start = new Date().valueOf()
  const timer = setInterval(() => {
    const now = new Date().valueOf()
    const past = now - start
    if (past <= 0) {
      console.log('count down end')
      clearInterval(timer)
    }
  }, 1000)
}

知道开始时间及倒计时的时长后,就能算出结束时间,也能在每次定时器回调中知道已经过去的时间。将计时的任务交给系统,js 只是轮询查流逝的时间,这样就可以避免误差。

这个是我提的 pr

这个方法也有一个问题,如果用户一直让页面处于非激活状态,在倒计时应该结束的那一刻,setInterval 可能也无法执行,需要到用户激活 tab,才能再次触发。

如果需要解决这个问题,感觉一个方案是包装成 app 来进行后台保活,一个方案是把状态信息存到服务器,当计时结束时服务器给 js 发消息。

2. js 定时器注意事项#

基础使用很简单,我们这里只说需要注意的几个点。

2.1 定时器嵌套#

setInterval 回调可以嵌入对另一个定时器的调用,为了减轻对性能的影响,一旦定时器嵌套超过 5 层,则浏览器将强制设置定时器最小时间间隔为 4ms。

2.2 确保代码执行时间短于定时器间隔#

定时器回调属于异步任务,只有调用定时器的主线程其他代码执行完毕才会执行回调函数。如果前面的代码执行时间超过定时器间隔,那么定时器就不会在正确的时间触发。所以浏览器无法保证一定在正确的时间触发回调。

2.3 未被激活的 tabs 的定时最小延迟 ≥ 1000ms#

为优化后台 tab 的加载损耗(以及降低耗电量),在未被激活的 tab 中定时器最小延迟时间为 1000ms。

2.4 最大延迟值#

浏览器以 32 位带符号整数存储延时,当延时大于 2147483647 毫秒 (大约 24.8 天) 时就会溢出,此时定时器会立即执行。

参考文章#

window.setTimeout - Web API 接口参考 | MDN

setInterval () - Web API 接口参考 | MDN

加载中...
此文章数据所有权由区块链加密技术和智能合约保障仅归创作者所有。