Javascript - 淺談this與Closure
網友kink問了一個問題:
$.fn.wait = function(time, type) {
time = time || 1000;
type = type || "fx";
return this.queue(type, function() {
var bb = this;
setTimeout(function() {
$(bb).dequeue();
}, time);
});
};
在以上這段程式setTimeout呼叫的函數裡,為什麼不能直接用$(this).dequeue(),而要先var bb = this; 再$(bb).dequeue()呢?
在Javascript,this是一個奧妙的關鍵字(至少對我來說很玄,我在學習jQuery後才慢慢搞清楚它的用法)。我們先避談this在自訂物件時的應用(那又要講一大串)。簡單來說,this代表函數的擁有者(Owner),例如在事件裡:
$("input:button").click(function() { alert($(this).attr("id")); });
this會指向觸發click事件的元素,原因是我們背地裡等同於將這個函數宣告成為元素的Method,元素成為函數的擁有者。setTimeout其實是window.setTimeout()函數,並不是某個物件觸發的事件,在這種情況下,this就會指向window,因此你可以試試:
setTimeout(function() { this.alert(this.document.body.innerHTML); }, 100);
又可以this.alert,又有this.document可用,可以證明this在當時指向的是window。
歸納一下,你可以想像成當函數被掛到某個物件上時,我們去呼叫它,this就會指向它所被掛載的對象(Owner),若前述這一點不成立,this則指向window。用個例子來說明:
function someFunc() { alert(this.document); }
var obj = new Object();
obj.doIt = someFunc;
obj.document = "Object's property";
someFunc();
obj.doIt();
執行這段程式,你會發現someFunc()會傳回[object](在FF中更明確,會是[object HTMLDocument]),而obj.doIt()則傳回"Object's property"。由此驗證,當函數變成了物件的一個Method時,this會指向該物件;直接呼叫函數本身,this指向window。
以上的實驗可以解釋不能在setTimeout中使用this的原因。既然不能直接用,初學Javascript的人(或像我一樣寫了七八年仍只用到皮毛的人)直覺想到的應該是宣告一個全域變數,然後在setTimeout中引用它。不過,用全域變數就要留意多次呼叫時變數名稱打架的問題。Javascript中,有一個更方便的解決方法--Closure。
Closure是程式語言上的一種概念(並不限Javascript),要徹底從學理上說清楚講明白並不是件容易的事,在此只對它的應用做點示範。(當然,真正的理由是我的能力範圍只到這個程度,XD)
var x = 1;
function say() { alert(x); }
x = 3;
say();
在這段程式裡,我們會得到3。但如果我們在宣告函數時x=1,就希望函數中的x不要受外界影響,怎麼辦?
function makeFunc(c) {
var x = c;
return function() { alert(x); }
}
x = 3;
var say1 = makeFunc(1);
say1();
var say2 = makeFunc(2);
say2();
在這個程式裡,我們宣告了makeFunc函數。函數裡設法產生一個Closure的環境,裡面宣告的var x接下參數c,跟當下建立的匿名函數綁在一起被傳回來。我們可以想像成,say1 = makeFunc(1)時,當場產生了一個匿名函數,並綁了值為1的x變數;say2 = makeFunc(2)時,現場產生了第二個匿名函數,並綁了值為2的x變數。這樣就滿足了每個函數需要擁有自已專屬變數的需求,會比用全域變數為每個函數保存一份乾淨俐落許多。
但可以想見這種寫法會耗用較多的記憶體,也有可能會導致瀏覽器發生記憶體洩漏(Memory Leaking)的狀況,IE6過去在這議題上惡名昭彰,但隨著不斷Update/Fix,差不多都更新改善過,我用過一些測試工具都已測不到Memory Leaking,應該不用太過擔心,在應用時有點警覺即可。
【延伸閱讀】