JavaScript 前端倒數計時糾偏實現

逆葵發表於2019-03-04

前端網頁倒數計時是非常常見的應用,我們在各大購物網站的秒殺活動中總是能見到它的身影。但是在實際情況中,我們常常會發現當網頁不重新整理、讓倒數計時程式持續執行時,顯示時間相比實際時間會越來越慢,相信大家也有在秒殺時間即將到來時不停重新整理頁面的經歷。原因自然也不難理解:倒數計時通常使用定時器(setTimeout 或者 setInterval )實現,而 JavaScript 的單執行緒特性使得主執行緒執行棧中出現阻塞時,任務佇列中的非同步任務並不能及時執行,因此瀏覽器並不能保證在定時器設定的時間結束後程式碼總是被準時執行,這就造成了倒數計時的偏差。

一般的解決方法是前端定時向伺服器傳送請求獲取最新的時間差來校準倒數計時時間,主動(程式裡設定定時請求)或被動的(F5 已被使用者按壞)區別而已。這個方法簡單但也有點粗暴,下面提供一種方法,能夠一定程度上不依賴服務端實現倒數計時的糾偏。程式碼非原創,時間久遠忘了出處,在此記錄一下學習過程以免遺忘。如有侵權請聯絡我。

首先我們需要模擬主執行緒阻塞的環境,同時又不能讓主執行緒一直阻塞:

setInterval(function(){ 
  let j = 0
  while(j++ < 100000000)
}, 0)
複製程式碼

然後是主要的程式碼:

const interval = 1000
let ms = 50000,  // 從伺服器和活動開始時間計算出的時間差,這裡測試用 50000 ms
let count = 0
const startTime = new Date().getTime()
let timeCounter
if( ms >= 0) {
  timeCounter = setTimeout(countDownStart, interval)
}
 
function countDownStart () {
   count++
   const offset = new Date().getTime() - (startTime + count * interval) // A
   const nextTime = interval - offset
   if (nextTime < 0) { 
       nextTime = 0 
   }
   ms -= interval
   console.log(`誤差:${offset} ms,下一次執行:${nextTime} ms 後,離活動開始還有:${ms} ms`)
   if (ms < 0) {
     clearTimeout(timeCounter)
   } else {
     timeCounter = setTimeout(countDownStart, nextTime)
   }
 }
複製程式碼

程式碼的基本原理並不複雜:通過遞迴呼叫 setTimeout 進行倒數計時操作的執行。而每次執行函式時會維護一個 count 變數,用以記錄已經執行過的倒數計時次數,使用程式碼 A 處的公式可計算出當前執行倒數計時的時間與實際應執行時間的偏差,進而可以計算出下次執行倒數計時的時間。

本文首發於我的部落格(點此檢視),歡迎關注。

相關文章