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

載入中......
此文章數據所有權由區塊鏈加密技術和智能合約保障僅歸創作者所有。