JavaScript 秘密花園
原文: http://bonsaiden.github.io/JavaScript-Garden/ ? 翻譯: 三生石上
其它
setTimeout
和
setInterval
由于 JavaScript 是異步的,可以使用
setTimeout
和
setInterval
來(lái)計(jì)劃執(zhí)行函數(shù)。

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ò)。
setInterval
的堆調(diào)用
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, 1000);
上面代碼中,
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í)行。
處理可能的阻塞調(diào)用
最簡(jiǎn)單也是最容易控制的方案,是在回調(diào)函數(shù)內(nèi)部使用
setTimeout
函數(shù)。
function foo(){ // 阻塞執(zhí)行 1 秒 setTimeout(foo, 1000); } foo();
這樣不僅封裝了
setTimeout
回調(diào)函數(shù),而且阻止了調(diào)用指令的堆積,可以有更多的控制。
foo
函數(shù)現(xiàn)在可以控制是否繼續(xù)執(zhí)行還是終止執(zhí)行。
手工清空定時(shí)器
可以通過(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);
清除所有定時(shí)器
由于沒(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,然后一把清除。
隱藏使用
eval
setTimeout
和
setInterval
也接受第一個(gè)參數(shù)為字符串的情況。 這個(gè)特性
絕對(duì)
不要使用,因?yàn)樗趦?nèi)部使用了
eval
。
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(a, b, c); }, 1000)注意: 雖然也可以使用這樣的語(yǔ)法
setTimeout(foo, 1000, a, b, c)
, 但是不推薦這么做,因?yàn)樵谑褂脤?duì)象的
屬性方法
時(shí)可能會(huì)出錯(cuò)。 (
譯者注:
這里說(shuō)的是屬性方法內(nèi),
this
的指向錯(cuò)誤)
結(jié)論
絕對(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 阻塞。