關於使用setTimeout的scope問題(回覆網友)

 Thu, 21 Aug 2008 11:06:13 +0800

setTimeout或是setInterval可以接受兩種參數,字串或是參考。如果是字串,他會用eval來處理這個字串,但是eval處理字串時,scope會......在他的execution context中,所以你用字串傳給setTimeout或setInterval,這樣他的scope會在global,接著執行this.shout()就找不到,發生錯誤,因為this這時是window物件,而他沒有叫做shout()的函數可以執行。

如果是參考,他就會執行這個參考,這樣不會有scope問題,例如:

function a() {
this.shout = function() {
alert("shouted");
}
this.wait1 = function() {
setTimeout(this.shout,100);
}
}
var b = new a();
b.wait1();

但是進一步來看,有一個問題,就是在shout方法中會找不到定義在a裡面的東西,因為傳給setTimeout/setInterval執行的參考,他的execution context是在window物件,所以在shout()裡面使用this時,這個this還是指向window物件:

function a() {
this.shoutStr = "shouted";
this.shout = function() {
alert(this.shoutStr);
}
this.wait1 = function() {
setTimeout(this.shout,100);
}
}
var b = new a();
b.wait1();

這樣alert的結果是undefined。但是如果在global定義一個shoutStr變數,他可以跑出來:

var shoutStr = "window Shout!";
function a() {
this.shoutStr = "shouted";
this.shout = function() {
alert(this.shoutStr);
}
this.wait1 = function() {
setTimeout(this.shout,100);
}
}
var b = new a();
b.wait1();

因為在瀏覽器這個host環境底下,window物件就是global物件。

但是我還是想取用在a()中間定義的東西,碰到這樣的情況,我通常只好用closure解決:

function a() {
this.shoutStr = "shouted";
var ooo = this;
this.shout = function() {
alert(ooo.shoutStr);
}
this.wait1 = function() {
setTimeout(this.shout,100);
}
}
var b = new a();
b.wait1();

詳細的原因,請參考ecma-262 edition3規格。因為是把this.shout當作參數傳給setTimeout,在setTimeout裡面他只是一個變數,把他後面加上()來執行而已。其實可以用一個方法來做實驗:

function a() {
this.shoutStr = "shouted";
var ooo = this;
this.shout = function() {
alert(ooo.shoutStr);
}
this.wait1 = function() {
c(this.shout);
}
}
var b = new a();
b.wait1();
function c(d) {
d();
}

在上面的例子裡,c()這個函數其實跟setTimeout是一樣的,你可以這樣實驗就清楚了。