為什麼要使用setTimeout來實現SetInterval?
我們知道setInterval以一定頻率來執行一個函數,但是這樣有一個問題,加入執行的這個函數相當耗時,超過了我們給定的週期時間,setInterval還會按照約定的時間來執行下次任務嗎?
答案是不會,setInterval會等到當前的任務執行完成後,再立即執行下一次的任務,看個例子
setInterval(function interval() {
for(let i = 0; i < 10000000000; i++);
console.log('done')
}, 1000)
打印語句出現的時間會超過1s,那麼第2s的打印操作也會隨着被推遲。參考下圖,setInterval每次的時間間隔是從任務開始時間開始算的,儘量與下一次任務的開始執行時間間隔和給定的時間間隔相同,一旦任務的執行時間超過了給定的時間間隔,那麼下一次任務會被推遲,下次任務會在本次任務結束後來執行。
如何使用setTimeout來模擬setInterval?
藉助與遞歸的思路,在上次任務結束後,立即安排下次任務的執行,這樣保證上一個任務的結束時間到下一個任務的開始時間和給定時間是相等的
function setInterval2(fn, ms, ...args) {
let timeId = null
function tick() {
timeId = setTimeout(() => {
fn(...args)
tick()
}, ms)
}
fn(...args)
tick()
return () => clearTimeout(timeId)
}
function setInterval3(fn, ms, ...args) {
fn(...args)
let timeId = setTimeout(function tick() {
fn(...args)
timeId = setTimeout(tick, ms)
}, ms)
return () => clearTimeout(timeId) // 方便隨時取消
}