文章目錄
- 一、背景
- 二、問題
- 1.用setInterval實作計時
- 2.用setTimeout實作計時
- 三、分析
- 1.出現誤差的原因
- 2.setInterval、setTimeout誤差的不同之處
- 四、模擬阻塞事件
- 1.setInterval
- 2.setTimeout
- 四、解決方案
一、背景
最近專案中加了一個計時器,我是用setInterval來實作計時的,用于顯示訂單剩余支付時間,但我發現,短時間還好,時間長了,計時器的誤差會很大,
二、問題
我現在來模擬一下這個問題
1.用setInterval實作計時
來看段代碼
var start = new Date().getTime(), count = 0;
var interval = setInterval(function () {
count++
console.log(new Date().getTime() - (start + count * 1000) + 'ms')
if(count == 10){
clearInterval(interval);
}
}, 1000)

可以看到,我列印的new Date().getTime() - (start + count * 1000) ,
也就是每次計時的誤差,理想情況下,應該是0,
2.用setTimeout實作計時
var start = new Date().getTime(), count = 0,interval = 1000;
var timer = setTimeout(doFunc,interval);
function doFunc(){
count++
console.log(new Date().getTime() - (start + count * 1000) + 'ms');
if(count < 10){
timer = setTimeout(doFunc,interval);
}
}

也是一樣的會出現誤差
三、分析
1.出現誤差的原因
setInterval、setTimeout實作都會出現誤差,這源于js的單執行緒,
他們的回呼函式并不是到時后立即執行,而是等系統計算資源空閑下來后才會執行,
setInterval、setTimeout都屬于宏任務,
有關事件回圈機制的內容,可參考文章
JavaScript中的Event Loop(事件回圈)機制(含圖解)
2.setInterval、setTimeout誤差的不同之處
setInterval指定的是“開始執行”之間的間隔,并不考慮每次任務執行本身所消耗的時間,因此實際上,兩次執行之間的間隔會小于指定的時間,比如,setInterval指定每 100ms 執行一次,每次執行需要 5ms,那么第一次執行結束后95毫秒,第二次執行就會開始,如果某次執行耗時特別長,比如需要105毫秒,那么它結束后,下一次執行就會立即開始,
為了確保兩次執行之間有固定的間隔,可以不用setInterval,而是每次執行結束后,使用setTimeout指定下一次執行的具體時間,
四、模擬阻塞事件
現在來模擬一下,加一些阻塞執行緒的代碼試試
1.setInterval
如:
//阻塞代碼
setInterval(function () {
var n = 0
while (n++ < 1000000000);
}, 1000)
var start = new Date().getTime(), count = 0;
var interval = setInterval(function () {
count++
console.log(new Date().getTime() - (start + count * 1000) + 'ms')
if(count == 10){
clearInterval(interval);
}
}, 1000)

2.setTimeout
//阻塞代碼
setInterval(function () {
var n = 0
while (n++ < 1000000000);
}, 1000)
var start = new Date().getTime(), count = 0,interval = 1000;
var timer = setTimeout(doFunc,interval);
function doFunc(){
count++
console.log(new Date().getTime() - (start + count * 1000) + 'ms');
if(count < 10){
timer = setTimeout(doFunc,interval);
}
}

可以看到加了一些阻塞執行緒的代碼后,誤差越來越嚴重,
在實際專案中,執行計時器的同時,會有很多其他異步阻塞事件,會導致倒計時功能不精確,
四、解決方案
我們需要進行誤差修正,也就是獲取到誤差的值,并且根據這個誤差值來動態調整我們執行回呼的間隔時間
- 計算誤差值
- 動態調整執行setTimeout的間隔
加上動態誤差修正
var start = new Date().getTime(), count = 0,interval = 1000;
var offset = 0;//誤差時間
var nextTime = interval - offset;//原本間隔時間 - 誤差時間
var timer = setTimeout(doFunc,nextTime);
function doFunc(){
count++
console.log(new Date().getTime() - (start + count * interval) + 'ms');
offset = new Date().getTime() - (start + count * interval);
nextTime = interval - offset;
if (nextTime < 0) { nextTime = 0; }
if(count < 10){
timer = setTimeout(doFunc,nextTime);
}
}
試試效果:

我把每次的nextTime列印出來看看:

可以看到每次的nextTime都會根據上次的誤差值來動態調整,以盡量減少整體的誤差,
參考
https://blog.csdn.net/lhz_333/article/details/105678627
本文鏈接https://blog.csdn.net/qq_39903567/article/details/115392972
轉載請註明出處,本文鏈接:https://www.uj5u.com/qianduan/272256.html
標籤:其他
