活動倒計時實現方案、setInterview和setTimeout的對比

聊一下活動倒計時的一些實踐方案與思考。方案總體來說分爲兩種:

方案一:依賴本地時間

當然不能簡單粗暴的直接獲取本地時間來倒計時,結果可能是每個用戶的倒計時時間千差萬別,而且用戶可以惡意更改本地時間去繞過倒計時操作。

方案:接口獲取到服務前當前時間以後,與本地時間做一個差值計算。循環中使用本地時間加時間差得出當前時間,來計算倒計時時間,並在一定頻率內更新服務器、本地時間差值。

// 接口獲取服務器當前時間,並計算與本地時間的時間差
const timeDiff = serverTime - new Date().getTime()
setTimeout(() => {
    // 當前時間 = 本地時間 + 時間差 
    const currTime = new Date().getTime() + timeDiff
    // 倒計時時間 = 截止時間 - 當前時間
    const countDown = endTime - currTime
    // 顯示倒計時信息
    setCountDownInfo(countDown)
}, 1000)

優點:

  • js倒計時有延遲。如果單純使用jssetTimeout倒計時,代碼運行時間越長偏差越大,本地時間的準確性就很可以實時糾正js倒計時的偏差。
  • pc端瀏覽器進入後臺運行,js計時器會變慢;移動端應用進入後臺運行,js計時器會直接停止,導致倒計時嚴重偏差。而依賴本地時間,就可以糾正這些偏差。

缺點:

用戶也是可以修改本地時間繞過計時器的。需要一定的頻率,獲取服務器時間更新時間差值,防止用戶修改本地時間

方案二:js倒計時

方案:獲取到服務器時間以後,js用setTimeout做倒計時,不依賴本地時間。

// 接口獲取服務器當前時間
let currTime = serverTime
setTimeout(() => {
    // 當前時間 = 服務器時間 循環累加
    currTime = currTime + 1000
    // 倒計時時間 = 截止時間 - 當前時間
    const countDown = endTime - currTime
    // 顯示倒計時信息
    setCountDownInfo(countDown)
}, 1000)
  • 優點:解決了依賴本地時間的弊端

  • 缺點:前邊提過,js定時器有偏差;頁面後臺運行,定時器會變慢。需要一定的頻率,獲取服務器當前時間矯正這些偏差

思考一:校準時間的頻率

無論上述哪種方案,無論矯正js定時器偏差還是更新時間差值防止修改本地時間頻率的設定都很重要,可以根據場景區分

1. 倒計時的重要性:

  • 直接控制秒殺或購買按鈕的倒計時,準確性要求最高,所以需要頻率相對高;
  • 其次就是浮標、活動入口按鈕、落地頁等提示類倒計時,不涉及主要業務邏輯,準確性要求低,可以頻率設定相對低;

2. 距離倒計時結束的時長

  • 距離還很長時,更新頻率高,沒必要而且很浪費服務器資源。
  • 倒計時馬上結束時,更新頻率很低,精度就會很差。

所以通常倒計時越接近結束時,準確性要求越高。可以根據倒計時時長,來動態設定更新頻率,倒計時距離結束越近,更新的頻率越高,並且設定頻率最高閾值;反之同理。

比如倒計時爲5分鐘時,30s更新一次:(5 * 60 * 1000) / 30 = 當前時間差(ms) / 當前頻率(s),就可以獲取動態的更新頻率

3. 當然頻率具體的數值,還要根據接口穩定性和用戶數量決定

思考二: 使用setTimeout還是setInterval

試想:

  • 如果setInterval定時器間隔時間總是小於操作執行時間時,事件隊列中就會排起隊 :本次循環事件未執行完,下次循環的事件已經添加進隊列中
  • 而且 當使用 setInterval時,僅當沒有該定時器的任何其他代碼實例時,纔將定時器代碼添加到隊列中

所以:

  • 丟幀: 那麼下次循環結束時,因爲隊列中已經有它的一個實例,就不會向隊列中添加事件了,所以這次事件執行就會丟失。
  • 而且當前的事件執行完畢後,就會馬上執行隊列中已經添加的事件,這兩次事件執行的時間間隔就會變小甚至無間隔

總結:

一定時間間隔重複做某些操作時,setInterval可能會出現丟幀操作之間無時間間隔的情況。所以儘量使用setTimeout,它可以保證事件在相同時間間隔內執行,並且不丟失事件

參考這篇文章

思考三: 爲什麼setTimeout時間間隔會有偏差

setTimeout只是負責準時的把事件添加進事件隊列中,但是如果js線程在忙於執行其他任務 (處理用戶交互、js代碼執行等),那麼這個事件就需要等待線程空閒了才能被執行

如果setTimeout遞歸實現倒計時,上述等待js線程產生的延遲就會在不停的循環中被疊加累計,延遲偏差就會越來越大,計時就會越來越不準。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章