來看這樣一個場景:使用 setInterval 定時器倒計時,突然來了一個長達三秒的任務,定時器會有一次不準,兩次丟失回調,導致少兩次計算時間。
// 在控制檯上輸入下面四行
var second = 0
setInterval(function() {
console.log(`setInterval ${++second}`, new Date().getTime())
}, 1000)
// 幾秒之後輸入下面代碼
function sleep(ms) {
const end = new Date().getTime() + ms
console.log('sleep start')
while (new Date().getTime() < end) {}
console.log('sleep end')
}
sleep(3000)
如圖所示,少兩次回調的執行。
requestAnimationFrame 實現定時器
requestAnimationFrame 傳入一個回調函數,該回調函數會在瀏覽器下一次重繪之前執行,詳情查看MDN文檔 window.requestAnimationFrame
/**
* 設置精度定時器
* @param {function} 回調函數
* @param {number} 延遲時間
* @return {number} 定時器ID
*/
function setIntervalPrecision(callback, delay) {
// 生成並記錄定時器ID
let obj = window.interValPrecisionObj || (window.interValPrecisionObj = { num: 0 })
obj.num++
obj['n' + obj.num] = true
var intervalId = obj.num
// 開始時間
var startTime = +new Date()
// 已執行次數
var count = 0
// 延遲時間
delay = delay || 0
;(function loop() {
// 定時器被清除,則終止
if (!obj['n' + intervalId]) return
// 滿足條件執行回調
if (+new Date() > startTime + delay * (count + 1)) {
count++
callback(count)
}
requestAnimationFrame(loop)
})()
return intervalId
}
/**
* 清除精度定時器
* @param {number} 定時器ID
*/
function clearIntervalPrecision(intervalId) {
if (window.interValPrecisionObj) {
delete window.interValPrecisionObj['n' + intervalId]
}
}
測試
// 在控制檯上輸入下面四行
setIntervalPrecision(function(val) {
console.log(`setIntervalPrecision ${val}`, new Date().getTime())
}, 1000)
// 幾秒之後輸入下面代碼
function sleep(ms) {
const end = new Date().getTime() + ms
console.log('sleep start')
while (new Date().getTime() < end) {}
console.log('sleep end')
}
sleep(3000)
任務阻塞結束後,會瞬間執行阻塞期間需要執行次數的回調,雖然倒計時頁面會卡三秒(js特性),但實際剩餘秒數不會出錯。