由于 JavaScript 是異步的,可以使用 setTimeout 和 setInterval 來(lái)計(jì)劃執(zhí)行函數(shù)。
注意: 定時(shí)處理不是 ECMAScript 的標(biāo)準(zhǔn),它們?cè)?DOM (文檔對(duì)象模型) 被實(shí)現(xiàn)。
function foo() {}
var id = setTimeout(foo, 1000); // 返回一個(gè)大于零的數(shù)字
當(dāng) setTimeout 被調(diào)用時(shí),它會(huì)返回一個(gè) ID 標(biāo)識(shí)并且計(jì)劃在將來(lái)大約 1000 毫秒后調(diào)用 foo 函數(shù)。
foo 函數(shù)只會(huì)被執(zhí)行一次。
基于 JavaScript 引擎的計(jì)時(shí)策略,以及本質(zhì)上的單線程運(yùn)行方式,所以其它代碼的運(yùn)行可能會(huì)阻塞此線程。
因此沒(méi)法確保函數(shù)會(huì)在 setTimeout 指定的時(shí)刻被調(diào)用。
作為第一個(gè)參數(shù)的函數(shù)將會(huì)在全局作用域中執(zhí)行,因此函數(shù)內(nèi)的 this 將會(huì)指向這個(gè)全局對(duì)象。
function Foo() {
this.value = 42;
this.method = function() {
// this 指向全局對(duì)象
console.log(this.value); // 輸出:undefined
};
setTimeout(this.method, 500);
}
new Foo();
注意:
setTimeout的第一個(gè)參數(shù)是函數(shù)對(duì)象,一個(gè)常犯的錯(cuò)誤是這樣的setTimeout(foo(), 1000), 這里回調(diào)函數(shù)是foo的返回值,而不是foo本身。 大部分情況下,這是一個(gè)潛在的錯(cuò)誤,因?yàn)槿绻瘮?shù)返回undefined,setTimeout也不會(huì)報(bào)錯(cuò)。
setTimeout 只會(huì)執(zhí)行回調(diào)函數(shù)一次,不過(guò) setInterval - 正如名字建議的 - 會(huì)每隔 X 毫秒執(zhí)行函數(shù)一次。
但是卻不鼓勵(lì)使用這個(gè)函數(shù)。
當(dāng)回調(diào)函數(shù)的執(zhí)行被阻塞時(shí),setInterval 仍然會(huì)發(fā)布更多的回調(diào)指令。在很小的定時(shí)間隔情況下,這會(huì)導(dǎo)致回調(diào)函數(shù)被堆積起來(lái)。
function foo(){
// 阻塞執(zhí)行 1 秒
}
setInterval(foo, 100);
上面代碼中,foo 會(huì)執(zhí)行一次隨后被阻塞了一秒鐘。
在 foo 被阻塞的時(shí)候,setInterval 仍然在組織將來(lái)對(duì)回調(diào)函數(shù)的調(diào)用。
因此,當(dāng)?shù)谝淮?foo 函數(shù)調(diào)用結(jié)束時(shí),已經(jīng)有 10 次函數(shù)調(diào)用在等待執(zhí)行。
最簡(jiǎn)單也是最容易控制的方案,是在回調(diào)函數(shù)內(nèi)部使用 setTimeout 函數(shù)。
function foo(){
// 阻塞執(zhí)行 1 秒
setTimeout(foo, 100);
}
foo();
這樣不僅封裝了 setTimeout 回調(diào)函數(shù),而且阻止了調(diào)用指令的堆積,可以有更多的控制。
foo 函數(shù)現(xiàn)在可以控制是否繼續(xù)執(zhí)行還是終止執(zhí)行。
可以通過(guò)將定時(shí)時(shí)產(chǎn)生的 ID 標(biāo)識(shí)傳遞給 clearTimeout 或者 clearInterval 函數(shù)來(lái)清除定時(shí),
至于使用哪個(gè)函數(shù)取決于調(diào)用的時(shí)候使用的是 setTimeout 還是 setInterval。
var id = setTimeout(foo, 1000);
clearTimeout(id);
由于沒(méi)有內(nèi)置的清除所有定時(shí)器的方法,可以采用一種暴力的方式來(lái)達(dá)到這一目的。
// 清空"所有"的定時(shí)器
for(var i = 1; i < 1000; i++) {
clearTimeout(i);
}
可能還有些定時(shí)器不會(huì)在上面代碼中被清除(譯者注:如果定時(shí)器調(diào)用時(shí)返回的 ID 值大于 1000), 因此我們可以事先保存所有的定時(shí)器 ID,然后一把清除。
setTimeout 和 setInterval 也接受第一個(gè)參數(shù)為字符串的情況。
這個(gè)特性絕對(duì)不要使用,因?yàn)樗趦?nèi)部使用了 eval。
注意: 由于定時(shí)器函數(shù)不是 ECMAScript 的標(biāo)準(zhǔn),如何解析字符串參數(shù)在不同的 JavaScript 引擎實(shí)現(xiàn)中可能不同。 事實(shí)上,微軟的 JScript 會(huì)使用
Function構(gòu)造函數(shù)來(lái)代替eval的使用。
function foo() {
// 將會(huì)被調(diào)用
}
function bar() {
function foo() {
// 不會(huì)被調(diào)用
}
setTimeout('foo()', 1000);
}
bar();
由于 eval 在這種情況下不是被直接調(diào)用,因此傳遞到 setTimeout 的字符串會(huì)自全局作用域中執(zhí)行;
因此,上面的回調(diào)函數(shù)使用的不是定義在 bar 作用域中的局部變量 foo。
建議不要在調(diào)用定時(shí)器函數(shù)時(shí),為了向回調(diào)函數(shù)傳遞參數(shù)而使用字符串的形式。
function foo(a, b, c) {}
// 不要這樣做
setTimeout('foo(1,2, 3)', 1000)
// 可以使用匿名函數(shù)完成相同功能
setTimeout(function() {
foo(1, 2, 3);
}, 1000)
注意: 雖然也可以使用這樣的語(yǔ)法
setTimeout(foo, 1000, 1, 2, 3), 但是不推薦這么做,因?yàn)樵谑褂脤?duì)象的屬性方法時(shí)可能會(huì)出錯(cuò)。 (譯者注:這里說(shuō)的是屬性方法內(nèi),this的指向錯(cuò)誤)
絕對(duì)不要使用字符串作為 setTimeout 或者 setInterval 的第一個(gè)參數(shù),
這么寫(xiě)的代碼明顯質(zhì)量很差。當(dāng)需要向回調(diào)函數(shù)傳遞參數(shù)時(shí),可以創(chuàng)建一個(gè)匿名函數(shù),在函數(shù)內(nèi)執(zhí)行真實(shí)的回調(diào)函數(shù)。
另外,應(yīng)該避免使用 setInterval,因?yàn)樗亩〞r(shí)執(zhí)行不會(huì)被 JavaScript 阻塞。