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

function foo() {} var id = setTimeout(foo, 1000); // 返回一個大于零的數字
當
setTimeout
被調用時,它會返回一個 ID 標識并且計劃在將來
大約
1000 毫秒后調用
foo
函數。
foo
函數只會被執行
一次
。
基于 JavaScript 引擎的計時策略,以及本質上的單線程運行方式,所以其它代碼的運行可能會阻塞此線程。 因此
沒法確保
函數會在
setTimeout
指定的時刻被調用。
作為第一個參數的函數將會在
全局作用域
中執行,因此函數內的
this
將會指向這個全局對象。
function Foo() { this.value = 42; this.method = function() { // this 指向全局對象 console.log(this.value); // 輸出:undefined }; setTimeout(this.method, 500); } new Foo();注意:
setTimeout
的第一個參數是
函數對象
,一個常犯的錯誤是這樣的
setTimeout(foo(), 1000)
, 這里回調函數是
foo
的
返回值
,而
不是
foo
本身。 大部分情況下,這是一個潛在的錯誤,因為如果函數返回
undefined
,
setTimeout
也
不會
報錯。
setInterval
的堆調用
setTimeout
只會執行回調函數一次,不過
setInterval
– 正如名字建議的 – 會每隔
X
毫秒執行函數一次。 但是卻不鼓勵使用這個函數。
當回調函數的執行被阻塞時,
setInterval
仍然會發布更多的回調指令。在很小的定時間隔情況下,這會導致回調函數被堆積起來。
function foo(){ // 阻塞執行 1 秒 } setInterval(foo, 1000);
上面代碼中,
foo
會執行一次隨后被阻塞了一分鐘。
在
foo
被阻塞的時候,
setInterval
仍然在組織將來對回調函數的調用。 因此,當第一次
foo
函數調用結束時,已經有
10
次函數調用在等待執行。
處理可能的阻塞調用
最簡單也是最容易控制的方案,是在回調函數內部使用
setTimeout
函數。
function foo(){ // 阻塞執行 1 秒 setTimeout(foo, 1000); } foo();
這樣不僅封裝了
setTimeout
回調函數,而且阻止了調用指令的堆積,可以有更多的控制。
foo
函數現在可以控制是否繼續執行還是終止執行。
手工清空定時器
可以通過將定時時產生的 ID 標識傳遞給
clearTimeout
或者
clearInterval
函數來清除定時, 至于使用哪個函數取決于調用的時候使用的是
setTimeout
還是
setInterval
。
var id = setTimeout(foo, 1000); clearTimeout(id);
清除所有定時器
由于沒有內置的清除所有定時器的方法,可以采用一種暴力的方式來達到這一目的。
// 清空"所有"的定時器 for(var i = 1; i < 1000; i++) { clearTimeout(i); }
可能還有些定時器不會在上面代碼中被清除( 譯者注 : 如果定時器調用時返回的 ID 值大于 1000), 因此我們可以事先保存所有的定時器 ID,然后一把清除。
隱藏使用
eval
setTimeout
和
setInterval
也接受第一個參數為字符串的情況。 這個特性
絕對
不要使用,因為它在內部使用了
eval
。
Function
構造函數來代替
eval
的使用。
function foo() { // 將會被調用 } function bar() { function foo() { // 不會被調用 } setTimeout('foo()', 1000); } bar();
由于
eval
在這種情況下不是被
直接
調用,因此傳遞到
setTimeout
的字符串會自
全局作用域
中執行; 因此,上面的回調函數使用的不是定義在
bar
作用域中的局部變量
foo
。
建議 不要 在調用定時器函數時,為了向回調函數傳遞參數而使用字符串的形式。
function foo(a, b, c) {} // 不要這樣做 setTimeout('foo(1,2, 3)', 1000) // 可以使用匿名函數完成相同功能 setTimeout(function() { foo(a, b, c); }, 1000)注意: 雖然也可以使用這樣的語法
setTimeout(foo, 1000, a, b, c)
, 但是不推薦這么做,因為在使用對象的
屬性方法
時可能會出錯。 (
譯者注:
這里說的是屬性方法內,
this
的指向錯誤)
結論
絕對不要
使用字符串作為
setTimeout
或者
setInterval
的第一個參數, 這么寫的代碼明顯質量很差。當需要向回調函數傳遞參數時,可以創建一個
匿名函數
,在函數內執行真實的回調函數。
另外,應該避免使用
setInterval
,因為它的定時執行不會被 JavaScript 阻塞。