出來混,遲早要還。今天寫JavaScript代碼,才知道一個迴圈裡的
函數共用同一閉包
三個閉套件共用一個變數。比如下面的代碼是錯的,不能正確報告每個事件對應的描述:
var div = document.getElementById("testDiv");
var events = {onclick: "clicked", onchange: "changed", onmouseover: "mouse over"};
for(var e in events){
div[e] = function(){
alert(events[e]);
};
}
試一下就知道。不管激發events列表裡的哪坨事件,alert彈出的視窗裡總是"mouse over"。
不明白JavaScript為什麼這樣處理迴圈和閉包的關係。哪位老大指點一下? 更新:隨便猜一下。JavaScript的Closure環境由靜態句法結構確定。也就是說,代碼一旦寫成,我們就知道函數的自由變數同哪些環境裡的變數綁定。這樣說來,上面的迴圈只申明了一個變數p和一個內建函式,從靜態句法結構來看,我們的確只有一個環境。因此,雖然運行時同一個內函數被調用多次,建立了多個閉包,這些閉包指向的都是同一個環境裡的同一個變數。這樣同JavaScript規定的閉包語義一致。
陽春版的修改辦法是利用函數定義建立新閉包,符合業內名言:任何電腦問題都能通過多加一層抽象解決。:-) Crockford Douglas 把多加的這層函數叫做因子函數(factor function)。
var div = document.getElementById("testDiv");
var events = {onclick: "clicked", onchange: "changed", onmouseover: "mouse over"};
for(var e in events){
div[e] = function(e){
return function(){
alert(events[e]);
};
}(e);
}
當然,如果到處建立因子函數,就繁瑣了。所以還是Dojo風格的調用來得清爽:
dojo.lang.forEach(events, function(e){
alert(e);
});
明眼老大們自然可以看出這是函數編程的風格。其實JavaScript本就是採用C語言句法的簡化版LISP,異常靈活。如果我們注意利用高端函數(加上閉包),便可像搭建積木那樣“拼裝”我們的程式,使代碼變得乾淨利落。Dojo架構裡實現了各式函數編程常用函數,比如map, reduce, filter, find, hitch, curry, 非常方便。再加上prototype chain, 對inspection的強大支援(光arguments.callee就讓俺愛不釋手了),以及call, apply, eval這三個超級函數,在JavaScript裡寫出功能完善的DSL也變得相對容易。 就算不搗騰DSL,JavaScript也有無數妙用。比如Dojo裡處理事件的代碼實現了AOP風格的建議功能,讓我們隨意操縱處理事件的時機,輕鬆截取任意事件的參數,哪怕是普通JavaScript函數調用的參數。而實現這些功能的代碼並不複雜。
從Dojo的實現也可以看出學習多種編程範式的重要性。Dojo裡支援函數編程,物件導向編程,Ruby風格的mixin, AOP風格的事件處理等從不同語言借鑒來的功能。如果Dojo的主程Alex Russell只懂JavaScript,就算他能倒背EMCAScript規範,恐怕也寫不出Dojo。要享受編程,避免一件本來很美好的事變成體力活,功夫永遠在詩外。