JavaScript——定時器為什麼是不精確的
前言
- 執行機制
- 實際探究
步驟
簡要回答
首先,我們要知道 setInterval 的執行機制,setInterval 屬於巨集任務,要等到一輪同步程式碼以及微任務執行完後才會走到巨集任務佇列,但是前面的任務到底需要多長時間,這個我們是不確定的
等到巨集任務執行,程式碼會檢查 setInterval 是否到了指定時間,如果到了,就會執行 setInterval,如果不到,那就要等到下次 EventLoop 重新判斷
當然,還有一部分不確定的因素,比如 setInterval 的時間戳小於 10ms,那麼會被調整至 10ms 執行,因為這是 setInterval 設計及規定,當然,由於其他任務的影響,這個 10ms 也會不精確
還有一些物理原因,如果使用者使用的裝置處於供電狀態等,為了節電,瀏覽器會使用系統定時器,時間間隔將會被調整至 16.6ms
深入探究版
1.超時限制為>=4ms
在現代瀏覽器中,由於回撥巢狀(巢狀級別至少為特定深度)或者經過一定數量的連續間隔而觸發連續呼叫時,setTimeout
/setInterval
呼叫至少每4ms被限制一次
function f(){}
function cb(){
f()
setTimeout(cb,0)
}
setTimeout(cb,0)
- 在Chrome和Firefox 第五次連續的呼叫就會被限制
- Safari鎖定了第六次通話
- Edge在第三次
- Gecko在
version56
已經這樣開始嘗試setInterval
(對setTimeout也一樣) 。In Chrome and Firefox, the 5th successive callback call is clamped; Safari clamps on the 6th call; in Edge its the 3rd one. Gecko started to treat setInterval() like this in version 56 (it already did this with setTimeout(); see below).
從歷史上來看,某些瀏覽器在執行此節流方式有所不同了,在setInterval
setTimeout
巢狀級別至少達到一定深度的情況下呼叫巢狀時,要想在現代瀏覽器實現0毫秒延遲可以使用postMessage
注意:最小延遲
DOM_MIN_TIMEOUT_VALUE
為4ms,同時DOM_CLAMP_TIMEOUT_NESTING_LEVEL
是5(dom固定超時巢狀級別)
2.在非活動tab卡,超時限制為>=1000ms
為了減少背景選項卡的負載(和相關的資源使用),在不活動的資源卡將超時限制為1000ms以下
firefox從版本5開始實施該行為(可通過dom.min_background_timeout_value
首選項調整1000ms常量)。Chrome從版本11開始實現該行為,自Firefox 14中出現錯誤736602以來,Android版Firefox的背景標籤使用的超時值為15分鐘,並且背景標籤也可以完全解除安裝
3.限制跟蹤超時指令碼
自Firefox 55起,跟蹤指令碼(例如Google Analytics(分析),Firefox通過其TP列表將其識別為跟蹤指令碼的任何指令碼URL )都受到了進一步的限制。在前臺執行時,節流最小延遲仍為4ms。但是,在後臺選項卡中,限制最小延遲為10,000毫秒(即10秒),該延遲在首次載入文件後30秒生效。
控制此行為的首選項是:
- dom.min_tracking_timeout_value:4
- dom.min_tracking_background_timeout_value:10000
- dom.timeout.tracking_throttling_delay:30000
4.逾期超時
除了固定值意外,當頁面(或OS /瀏覽器本身)忙於其他任務時,超時還會在以後觸發。要注意的一個重要情況是,直到呼叫的執行緒setTimeout()終止,函式或程式碼段才能執行。例如:
function foo() {
console.log('foo has been called');
}
setTimeout(foo, 0);
console.log('After setTimeout');
// After setTimeout foo has been called
這是因為即使setTimeout以零的延遲被呼叫,它也被放置在佇列中並計劃在下一個機會執行。不是立即。當前執行的程式碼必須在執行佇列中的功能之前完成,因此生成的執行順序可能與預期的不同