11.28 JavaScript 關於setTimeout與setInterval的小研究

在開發功能“軌跡播放”時,遇到了一個情況。
原先同事已經開發了一版,這次有個新功能:點擊線上任意一點後可以從點擊處重新播放。


看了一下原來的版本,發現同時使用了setTimeout和setInterval,兩者配合實現點線播放。
簡單結構如下

 function test() {
setInterval(function () {
console.log("interval");
//省略插值方法得到arr
(...)
play(arr);
}, 2000);
}
function play(arr) {
setTimeout(function () {
play(arr);
console.log("setTimeout");
}, 40);
}

我覺得這個結構欠妥,兩個定時器配合必定會出現失誤!因此重構了一版,將兩個定時器改為一個,用setInterval解決。
但是此時我並不知道欠妥欠在什麼地方,缺乏理論支持,現在閒下來仔細研究了一下

0|1找問題

在仔細研究了舊版本後,我先把舊版本結構扒了出來,排除其他因素,自己模擬了一個簡單版(就是上面的代碼)
setTimeout:在執行時,是在載入後延遲指定時間後,去執行一次表達式,僅執行一次
setInterval:在執行時,它從載入後,每隔指定的時間就執行一次表達式

  • 實驗一:在使用setInterval和setTimeout方法上,並沒有什麼問題,決定跑一下,結果如下
JavaScript 關於setTimeout與setInterval的小研究

從結果得出兩點結論

  1. setTimeout與setInterval並不是50倍速度配合運行著
  2. 兩次interval間,timeout運行的次數越來越多,表明setInterval運行間隔越來越長,延遲越來越大
  • 實驗二:加一點人工干預再執行
 function test() {
setInterval(function () {
console.log("interval");
play();
}, 2000);
}
function play() {
//延遲執行
for (var i = 0; i < 100000000; i++) {

}
setTimeout(function () {
play();
console.log("setTimeout");
}, 40);
}
JavaScript 關於setTimeout與setInterval的小研究

從結果得出兩點結論

  1. setInterval可能會隨函數處理時間,減少間隔
  2. 推測,因為Javascript是單線程的,setInterval和setTimeout是放隊列裡執行的,很容易受到回調事件影響
  • 實驗三:拖動縮放瀏覽器
JavaScript 關於setTimeout與setInterval的小研究

從結果得出結論

  1. 當瀏覽器標籤切換到其他頁面,或者瀏覽器最小化,會影響計時器,兩者會出現間隔減小

0|1涉及知識點

綜上實驗結果,網上搜集了一些資料能說明問題:

  1. JavaScript是單線程,但是瀏覽器是多線程,Javascript是瀏覽器多線程中的一個線程。(圖參考自:https://www.cnblogs.com/tesky0125/p/4619549.html)
JavaScript 關於setTimeout與setInterval的小研究

  1. Javascript會把執行的回調函數、瀏覽器的觸發事件、UI渲染事件,先放到隊列中,隊列根據先進先出的規則,依次執行他們,當執行到隊列中的setInterval時很難保證其與setTimeout同步關係還保持。
  2. setInterval無視代碼錯誤:代碼報錯,但是setInterval依舊會按時執行,不會中斷。
  3. setInterval無視網絡延遲:如果調用ajax或其他服務,他不會管是否返回回調,會繼續按時執行。
  4. setInterval不保證執行:因為setInterval會定時執行,如果函數邏輯很長,間隔時間內執行不完,後續方法會被拋棄。
  5. 會受瀏覽器狀態影響,tab切換、最小化等

0|1解決方案

在做軌跡播放時,setInterval的延遲還在可接受範圍之內,但是網上給出的最佳解決方案是用setTimeout做。
setTimeout只會執行一次,在執行完成後,重新啟動新的Timeout,時間runtime計算設置為差時,減少出現間隔越來越大的情況

 function test() {
//runTime,計算差時
runTime = 1000 - 執行耗時;
setTimeout(callback, runTime);
}
setTimeout(test, 1000);


分享到:


相關文章: