JavaScript Closure
這篇想寫很久了,test case的檔案也寫好超過一個月了,這次用了3(?)個case來說明,以下每個test-case都有三個h2標籤,然後透過getElementsByTagName來取得這三個h2,接著用for迴圈來對每個h2加上click事件,click後會alert i的值出來,i是for迴圈的索引值。首先來看一下第一個case的script:
var subHeads = document.getElementsByTagName('h2');
for (var i=0; i<subHeads.length; i++) {
subHeads[i].onclick = function () {
alert(i);
};
}這個case中,使用非常直觀的想法,不過他的結果是每個h2 click下去都會alert "3"出來,實際上每個click都是執行一個function,這個function會執行alert function,並且送 i 這個變數作為輸入值,不過實際上使用者click動作發生時,迴圈已經跑過一遍,而且 i 的值已經變成3了,所以你不管click哪一個h2,實際上都是做 alert(3) 這個動作。
要解決這個問題,有兩個方法可以處理,第一個是利用function來做closure把變數的scope獨立出來,另一個方法是用eval來做function,首先來看第一個作法:
var subHeads = document.getElementsByTagName('h2');
for (var i=0; i<subHeads.length; i++) {
(function () {
var ii = i;
subHeads[i].onclick = function () {
alert(ii);
};
})();
}這個方法是用匿名function把 ii 這個變數的scope獨立起來,而 ii 的值就是在這個匿名function執行時 i 的值,這樣每個click function裡面的 ii 就都各自獨立,不會互相影響到。再來看第二個作法:
var subHeads = document.getElementsByTagName('h2');
for (var i=0; i<subHeads.length; i++) {
eval("subHeads[i].onclick = function () {" +
" alert("+i+");" +
"};");
}可以看到整個event function的指派都是用eval來達成的,比較特別的是要用 i 時,我是跳脫字串,直接用 i變數 的值,這個作法其實是讓每個click function的內容都不太一樣(alert的輸入值不同)。而除了這兩個方法之外,我還蠻喜歡把屬性加到DOM的elementNode上的,所以來看看我習慣的作法:
var subHeads = document.getElementsByTagName('h2');
for (var i=0; i<subHeads.length; i++) {
subHeads[i].attr_i = i;
subHeads[i].onclick = function () {
alert(this.attr_i);
};
}在這個範例理,我先在 h2 node 下面加上 attr_i 這個屬性,值就是 i 當時的值,而click function內就是送 attr_i 給alert function,這樣結果也可以如我們所預期。不過如果是用jQuery的話就要注意了,來看看最後這個jQuery的例子:
var subHeads = $('h2');
for (var i=0; i<subHeads.length; i++) {
subHeads.eq(i).attr_i = i;
subHeads.eq(i).click(function () {
alert($(this).attr_i);
});
}因為jQuery沒有cache機制,所以上面這個例子會發生錯誤, $(this).attr_i 會不存在,會alert出 'undefined' ,要新增屬性的話要直接加到最基本的 DOM Node 上,不可以放在jQuery物件上,要存取DOM Node可以用subHead.get(0)或是subHead[0],後者我沒測試過就是。
引用(http://othree.net/cgi-bin/mt/mt-tb.cgi/538)
目前無人引用。

1由Jacky在 2008-09-27 10:30:39 發表:
應該用 $(this).attr('attr_i') 吧.
另外,如果想存非 string 的資料,可以用 $(this).data('attr_i', dataObject);
2由othree.net在 2008-09-27 20:38:42 發表:
data()我是沒用過,看來可以用,不過attr我確定他是會寫到屬性裡面,但是不正確(標準內有)的屬性名稱不會生效。
3由Jacky在 2008-09-27 22:17:19 發表:
oh? 但我試過是可以自訂attribute的~
寫了個簡單測試http://jackysee.googlepages.com/attr.html
4由othree.net在 2008-09-29 16:56:23 發表:
如果我記憶沒錯的話,那就可能是版本差異了@@