搞清楚lexical scope與closure
Thu, 04 Sep 2008 13:38:19 +0800之前沒有徹底搞懂這兩者的差別,今天回頭去看Martin Fowler講Closure的文章以及wikipedia對closure的解釋時,總算弄清楚了(之前偷懶,沒把東西好好看完)。
lexical scope的特性,讓我們可以用區域變數的方式,把變數當作一個function物件的private member,但是又可以用一個function當作getter/setter來存取他。例如:
function Bean() { var X; var Y; this.setX = function(x) { X=x; } this.getX = function() { return X; } this.setY = function(y) { Y = y; } this.getY = function() { return Y; } } var a = new Bean(); a.setX(3); a.setY(3); alert(a.getX()+""+a.getY());
另一個更常用的時機,是使用setTimeout。例如:
function TT() { this.lm = "the message"; var localthis = this; this.startT = function() { setTimeout(function(){alert(localthis.lm);},500); } } var d = new TT("the message"); d.startT();
在傳給setTimeout的匿名函數裡面使用this的話,這個this會指向Window物件,所以要在這裡要先把他指定給一個區域變數,這樣就可以讓存在this.lm裡的訊息,透過localthis來取用。
像這樣可以用函數把區域變數攜出到別的scope使用,還只是lexical scope的應用,要產生closure,需要一些特別的條件:
var m1 = "free variable to be bound."; var m2 = "another free variable to be bound."; function a(free) { return function(){setTimeout(function(){alert(free);},500);} } var b = a(m1); m1 = null; b(); var c = a(m2); m2 = null; c(); b(); c();
執行這一段程式,結果會依序跳出"free variable to be bound", "another free variable to be bound", "free variable to be bound", "another free variable to be bound"訊息。closure發生的要件就是,一個free variable透過一段敘述,結果就被bound在這一段敘述裡了。透過這一段程式可以發現,不論傳給a()什麼,什麼就被綁在裡面了,即是傳給他的變數之後用assign的方式釋放或改變了(理論上),被綁在a()裡面的東西也不會改變。
許多討論javascript closure的網站,都會舉類似下面的意外使用到closure的狀況:
function addEvent(node) { node.onclick = function() { node.innerHTML += "bla ";//here the leak started } }
像上述的程式,你對多少dom node做處理,就有多少dom node會被keep在記憶體中。數量多的話.....
另外,第一個closure的例子透過b=null;c=null;這樣應該可以釋放被「封絕」的東西;而第二個例子透過node.onclick=null;也應該可以。但是在舊版的IE(6?)就不是這一回事。因為HTML部份跟Script是分開的兩個ActiveX元件,可以做出Closure,但是沒有辦法這樣釋放記憶體......結果就......建議你不要使用Closure。(不然就會變成火炬喔)
2008-10-4 0:24 補充
最近在看Crockford的《Javascript: 優良部分》,看起來他並沒有把「參數傳入函式」當作形成closure的必要條件,所以像「參數傳入函式」這種狀況應該算是closure的一種,只是side effect比較大。
2008-12-15 16:04補充
今天蒙Josh兄指正我的例子有問題(第三個例子),的確也有問題,我不應該用String做例子,修改後如下:
var str = {a:"test"}; function a(v) { v['a'] = "test2"; } a(str); alert(str.a); function b(v) { var b = v; this.getB = function() { return b.a; } } var c=new b(str); str = null; alert(c.getB());
上面這一段code才能表現出我要說的意思,對於DOM物件也是類似的狀況。我另外測試了所有的型別,發現一些有趣的東西,我用另一篇文章寫好了。
2008-12-16 10:28補充
另外寫了一篇「對於Javascript所有型別在Closure中表現的測試」,可以參考一下。