在 Chrome 中除了正常使用的消息隊(duì)列之外,還有另外一個(gè)消息隊(duì)列,這個(gè)隊(duì)列中維護(hù)了需要延遲執(zhí)行的任務(wù)列表,包括了定時(shí)器和 Chromium 內(nèi)部一些需要延遲執(zhí)行的任務(wù)。所以當(dāng)通過(guò) JavaScript 創(chuàng)建一個(gè)定時(shí)器時(shí),渲染進(jìn)程會(huì)將該定時(shí)器的回調(diào)任務(wù)添加到延遲隊(duì)列中。
具體來(lái)說(shuō),是一個(gè)hashmap結(jié)構(gòu),等到執(zhí)行這個(gè)結(jié)構(gòu)的時(shí)候,會(huì)計(jì)算hashmap中的每個(gè)任務(wù)是否到期了,到期了就去執(zhí)行,直到所有到期的任務(wù)都執(zhí)行結(jié)束,才會(huì)進(jìn)入下一輪循環(huán)。所以延遲隊(duì)列和正常的消息隊(duì)列是不沖突的。
當(dāng)前任務(wù)執(zhí)行時(shí)間過(guò)久,會(huì)延遲定時(shí)任務(wù)的執(zhí)行
通過(guò) setTimeout 設(shè)置的回調(diào)任務(wù)被放入了消息隊(duì)列中并且等待下一次執(zhí)行,這里并不是立即執(zhí)行的;要執(zhí)行消息隊(duì)列中的下個(gè)任務(wù),需要等待當(dāng)前的任務(wù)執(zhí)行完成,由于當(dāng)前這段代碼要執(zhí)行 5000 次的 for 循環(huán),所以當(dāng)前這個(gè)任務(wù)的執(zhí)行時(shí)間會(huì)比較久一點(diǎn)。這勢(shì)必會(huì)影響到下個(gè)任務(wù)的執(zhí)行時(shí)間。
function bar() {
/* */
}
function foo() {
setTimeout(bar, 0)
for (let i = 0; i < 5000; i++) {
//do something
}
}
setTimeout嵌套調(diào)用,系統(tǒng)會(huì)設(shè)置最短時(shí)間間隔為4毫秒
在 Chrome 中,定時(shí)器被嵌套調(diào)用 5 次以上,系統(tǒng)會(huì)判斷該函數(shù)方法被阻塞了,如果定時(shí)器的調(diào)用時(shí)間間隔小于 4 毫秒,那么瀏覽器會(huì)將每次調(diào)用的時(shí)間間隔設(shè)置為 4 毫秒。
下面是Chromium實(shí)現(xiàn)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);
base::TimeDelta interval_milliseconds =
std::max(base::TimeDelta::FromMilliseconds(1), interval);
if (interval_milliseconds < kMinimumInterval &&
nesting_level_ >= kMaxTimerNestingLevel)
interval_milliseconds = kMinimumInterval;
if (single_shot)
StartOneShot(interval_milliseconds, FROM_HERE);
else
StartRepeating(interval_milliseconds, FROM_HERE);
未激活的頁(yè)面,setTimeout執(zhí)行最小間隔是1000毫秒
未被激活的頁(yè)面中定時(shí)器最小值大于1000毫秒,也就是說(shuō),如果當(dāng)前標(biāo)簽頁(yè)處于未激活的狀態(tài),那么定時(shí)器最小的時(shí)間間隔是1000毫秒,目的是為了優(yōu)化后臺(tái)頁(yè)面的加載損耗以及降低耗電量。
延遲執(zhí)行時(shí)間有最大值
Chrome、Safari、Firefox 都是以 32 個(gè) bit 來(lái)存儲(chǔ)延時(shí)值的,32bit 最大只能存放的數(shù)字是 2147483647 毫秒,這就意味著,如果 setTimeout 設(shè)置的延遲值大于 2147483647 毫秒(大約 24.8 天)時(shí)就會(huì)溢出,這導(dǎo)致定時(shí)器會(huì)被立即執(zhí)行。
function showTime() {
console.log(new Date())
}
let timer = setTimeout(showTime, 2147483648); // 立即執(zhí)行
setTimeout回調(diào)函數(shù)中的this指向
如果被 setTimeout 推遲執(zhí)行的回調(diào)函數(shù)是某個(gè)對(duì)象的方法,那么該方法中的 this 關(guān)鍵字將指向全局環(huán)境,而不是定義時(shí)所在的那個(gè)對(duì)象。
總結(jié)
- setTimeout嵌套調(diào)用5次以上,時(shí)間間隔最小為4毫秒;
- 在未激活頁(yè)面運(yùn)行,最小時(shí)間間隔為1000毫秒;
- 最大時(shí)間間隔為32個(gè)bit可存儲(chǔ)的值,也就是2147483647 毫秒(大約 24.8 天)。