定義
定時器,用來指定某個函數在多少毫秒之後執行。它會返回一個整數,表示定時器的編號,同時你還可以通過該編號來取消這個定時器。
實現
定時器的實現
- 當通過 JavaScript 調用 setTimeout 設置回調函數的時候,渲染進程將會創建一個回調任務,包含了回調函數 showName、當前發起時間、延遲執行時間。
- 創建好回調任務之後,再將該任務添加到延遲執行隊列中
延遲隊列:維護了需要延遲執行的任務列表,包括了定時器和 Chromium 內部一些需要延遲執行的任務
- 處理完消息隊列中的一個任務之後,就開始執行 ProcessDelayTask 函數。ProcessDelayTask 函數會根據發起時間和延遲時間計算出到期的任務,然後依次執行這些到期的任務。等到期的任務執行完成之後,再繼續下一個循環過程。一個完整的定時器就實現了
ProcessDelayTask函數:專門用來處理延遲執行任務的
刪除定時器的實現
直接從 delayed_incoming_queue 延遲隊列中,通過 ID 查找到對應的任務,然後再將其從隊列中刪除掉就可以了。
存在的問題
如果當前任務執行時間過久,會影響定時器任務的執行,setTimeout是不準時的
通過前面setTimeout的實現來看,延遲隊列的檢查是在當前任務執行完之後進行的,如果當前任務執行過久,勢必會影響後續的執行,可以這麼認爲,setTimeout設置的時間是將延遲任務由延遲任務隊列添加到消息隊列的時間而不是執行延遲任務的時間
如果 setTimeout 存在嵌套調用,那麼系統會設置最短時間間隔爲 4 毫秒
定時器函數裏面嵌套調用定時器,也會延長定時器的執行時間,可以先看下面的這段代碼:
function cb() { setTimeout(cb, 0); } setTimeout(cb, 0);
在 Chrome 中,定時器被嵌套調用 5 次以上,系統會判斷該函數方法被阻塞了,如果定時器的調用時間間隔小於 4 毫秒,那麼瀏覽器會將每次調用的時間間隔設置爲 4 毫秒。實現的代碼
static const int kMaxTimerNestingLevel = 5; // Chromium uses a minimum timer interval of 4ms. We'd like to go // lower; however, there are poorly coded websites out there which do // create CPU-spinning loops. Using 4ms prevents the CPU from // spinning too busily and provides a balance between CPU spinning and // the smallest possible interval timer. static constexpr base::TimeDelta kMinimumInterval = base::TimeDelta::FromMilliseconds(4);
延時執行時間有最大值
如果 setTimeout 設置的延遲值大於 2147483647 毫秒(大約 24.8 天)時就會溢出,那麼相當於延時值被設置爲 0 了,這導致定時器會被立即執行。
未激活的頁面,setTimeout 執行最小間隔是 1000 毫秒
那就是未被激活的頁面中定時器最小值大於 1000 毫秒,也就是說,如果標籤不是當前的激活標籤,那麼定時器最小的時間間隔是 1000 毫秒,目的是爲了優化後臺頁面的加載損耗以及降低耗電量。這一點你在使用定時器的時候要注意。
參考
瀏覽器工作原理與實踐
whatwg規範