聊一下活動倒計時的一些實踐方案與思考。方案整體來講分爲兩種:segmentfault
固然不能簡單粗暴的直接獲取本地時間來倒計時,結果多是每一個用戶的倒計時時間千差萬別,並且用戶能夠惡意更改本地時間去繞過倒計時操做。瀏覽器
方案:接口獲取到服務前當前時間之後,與本地時間作一個差值計算。循環中使用本地時間加時間差得出當前時間,來計算倒計時時間,並在必定頻率內更新服務器、本地時間差值。服務器
// 接口獲取服務器當前時間,並計算與本地時間的時間差
const timeDiff = serverTime - new Date().getTime()
setTimeout(() => {
// 當前時間 = 本地時間 + 時間差
const currTime = new Date().getTime() + timeDiff
// 倒計時時間 = 截止時間 - 當前時間
const countDown = endTime - currTime
// 顯示倒計時信息
setCountDownInfo(countDown)
}, 1000)
複製代碼
js
倒計時有延遲。若是單純使用js
的setTimeout
倒計時,代碼運行時間越長誤差越大,本地時間的準確性就很能夠實時糾正js
倒計時的誤差。js
計時器會變慢;移動端應用進入後臺運行,js計時器會直接中止,致使倒計時嚴重誤差。而依賴本地時間,就能夠糾正這些誤差。用戶也是能夠修改本地時間繞過計時器的。須要必定的頻率,獲取服務器時間更新時間差值,防止用戶修改本地時間。ui
js
倒計時方案:獲取到服務器時間之後,js用
setTimeout
作倒計時,不依賴本地時間。spa
// 接口獲取服務器當前時間
let currTime = serverTime
setTimeout(() => {
// 當前時間 = 服務器時間 循環累加
currTime = currTime + 1000
// 倒計時時間 = 截止時間 - 當前時間
const countDown = endTime - currTime
// 顯示倒計時信息
setCountDownInfo(countDown)
}, 1000)
複製代碼
優勢:解決了依賴本地時間的弊端線程
缺點:前邊提過,js定時器有誤差;頁面後臺運行,定時器會變慢。須要必定的頻率,獲取服務器當前時間矯正這些誤差code
不管上述哪一種方案,不管矯正js
定時器誤差仍是更新時間差值防止修改本地時間,頻率的設定都很重要,能夠根據場景區分cdn
因此一般倒計時越接近結束時,準確性要求越高。能夠根據倒計時時長,來動態設定更新頻率,倒計時距離結束越近,更新的頻率越高,而且設定頻率最高閾值;反之同理。server
好比倒計時爲5分鐘時,30s更新一次:(5 * 60 * 1000) / 30 = 當前時間差(ms) / 當前頻率(s)
,就能夠獲取動態的更新頻率blog
setTimeout
仍是setInterval
setInterval
定時器間隔時間老是小於操做執行時間時,事件隊列中就會排起隊 :本次循環事件未執行完,下次循環的事件已經添加進隊列中setInterval
時,僅當沒有該定時器的任何其餘代碼實例時,纔將定時器代碼添加到隊列中必定時間間隔重複作某些操做時,setInterval
可能會出現丟幀和操做之間無時間間隔的狀況。因此儘可能使用setTimeout,它能夠保證事件在相同時間間隔內執行,而且不丟失事件。
參考這篇文章
setTimeout
時間間隔會有誤差setTimeout
只是負責準時的把事件添加進事件隊列中,可是若是js
線程在忙於執行其餘任務 (處理用戶交互、js
代碼執行等),那麼這個事件就須要等待線程空閒了才能被執行。
若是setTimeout
遞歸實現倒計時,上述等待js
線程產生的延遲就會在不停的循環中被疊加累計,延遲誤差就會愈來愈大,計時就會愈來愈不許。