可以, 但小心使用.
閉包也許是 JS 中最有用的特性了. 有一份比較好的介紹閉包原理的文檔.
有一點需要牢記, 閉包保留了一個指向它封閉作用域的指針, 所以, 在給 DOM 元素附加閉包時, 很可能會產生循環引用, 進一步導致內存泄漏. 比如下面的代碼:
function foo(element, a, b) { element.onclick = function() { /* uses a and b */ }; }
這里, 即使沒有使用 element
, 閉包也保留了 element
, a
和 b
的引用, . 由於 element
也保留了對閉包的引用, 這就產生了循環引用, 這就不能被 GC 回收. 這種情況下, 可將代碼重構為:
function foo(element, a, b) { element.onclick = bar(a, b); } function bar(a, b) { return function() { /* uses a and b */ } }
————— 谷歌js編碼規范
這是谷歌js編碼規范里的。"閉包",是個繞不開的話題,我查閱了不少資料,各種解釋都有,關於為什么會造成內存泄漏,也都介紹的很晦澀,沒有把原理講透,在這里,我就這兩個問題詳細講一下。
首先講閉包。閉包簡而言之,就是一個函數(fa),的內部函數(fb)被fa外的變量引用,就形成了一個閉包;下面給出兩種事例:
1)
function fa() { function fb() { alert("hello word"); } return fb; } var myfun = fa(); myfun();
2)
function fa() { var e = document.getElementById("id"); e.event = function () { alert("hello word"); }; }
這兩種形式的形成閉包的機制不同,一個通過一個return 返回這個內部函數,從而被包外引用,而另一個則是通過 e,這個docment這個宿主對象的事件而完成外部引用。這兩種形式形成的結果就是fa這個函數的的內部函數可以被fa的外部變量所引用,這就形成了閉包。
閉包講到這里我想大家琢磨一下應該很清楚了,下面我們來分析下這個內存泄漏是怎么形成的。很多資料都說循環引用,IE的計數式的垃圾回收機制,但我相信,這些概念很模糊,到底是怎么們回事,我們下面詳細來剖析。
垃圾回收機制現在很成熟了,但早期的IE版本里(ie4-ie6),對宿主對象(也就是document對象)采用是計數的垃圾回收機制,閉包導致內存泄漏的一個原因就是這個算法的一個缺陷。循環引用會導致沒法回收,這個循環引用只限定於有宿主對象參與的循環引用,而js對象之間即使形成循環引用,也不會產生內存泄漏,因為對js對象的回收算法不是計數的方式。
首先我們明確下內存泄漏的概念:內存里不能被回收也不能被利用的空間即為內存泄漏。為什么不能被回收呢?不符合內存回收的算法;為什么不能被利用呢?在棧上沒有指向他的指針。在這里我簡單的講一下堆和棧的關系:
function fa() { var o = new Object(); } fa();
我們看這段代碼執行的時候發生了什么

我們看到,棧上只是存了一個指針,指針就是堆上對象的的地址;我們的程序通過這個指針句可以操作堆上的對象。棧上的這個指針是自動管理的,當函數退出后,就銷毀了;這樣程序就在沒辦法訪問到堆上的這個對象了,而堆上的這個對象這個時候就會被我們的GC自動回收了;如果回收不了,就是內存泄漏了。
講到這里大家對內存泄露應該是有所了解了,對於計數回收方式大家查下資料,相信大家根據上面講的應該可以看明白了,這里不再詳細描述。下面我們着重描述下內存泄露的原因,大家先看下面的代碼:
function fa() { var a = "hello word"; return function () { alert(a); } } var o = fa(); o();
這段代碼輸出hello word,這說明什么?說明在堆上的”hello word‘ 沒有被回收,什么原因?因為o這個函數還要引用這個變量。下面我們用計數的GC方式來逐句分析程序的代碼
在堆上有兩個對象 一個是 hello word 我們叫做O1,匿名函數function(){alert(a);}我們叫做O2

當執行
var a = "hello word"; 的時候 O1的計數為1;
當執行
return function () {
alert(a);
}
的時候 O2的計數變為1;
當執行完fa這個函數后 棧上的 var a 會被銷毀,同是他指向的對象計數減1,這樣問題就來了,這樣O1的計數變為0了,那不被gc回收了嘛?怎么還會輸出"hello word"?
原來在執行
return function () {
alert(a); }
這個函數的時候,為了保持函數對這個變量的引用,在這個匿名函數的作用域鏈上加了一個對O1的引用,這樣 其實 O1的計數在變成了2,在a被銷毀后,O1減變成了1而不是0.
那么O1時候被回收呢?當O2被回收的時候。O2什么時候被回收呢?當指向他的var o 從棧上消失的時候。
好,講到這里,原理我們講完了下面我們就看下
function fa() { var e = document.getElementById("id"); e.event = function () { alert("hello word"); }; }
這段代碼為什么會造成內存泄露
var e = document.getElementById("id"); 執行這段代碼的時候 右邊(O1)的對象計數變為了1
執行這段代碼的時候
e.event = function () {
alert("hello word"); };
匿名函數(O2)的計數變了1;對象O1的計數變了2;
當函數fa執行完畢時 棧上的指針var e 消失,他指向的對象 O1的計數減1變為了1;這樣當函數執行完畢,O1、O2的這兩個對象的計數都為1,根據計數的回收算法,就都留在內存里了不能被GC回收了,就造成了內存泄漏。
上面說法不完全正確,實際上執行完fa后O2的計數是2,這個大家可以想一下原因。
其實fa里面的宿主對象只是真正對象一個副本,當執行
e.event 這句指令的時候 做了兩件事,一個是副本的對象指向O2 這時O2的計數加1,真正的宿主對象又指向這個O2,這個O2的計數再加1 變為了2
所以 在fa的外面執行 e.event=null 的時候,這時O2計數減1變為了1, 這時候,棧上再沒有指向O2的指針了,所以O2的計數再沒有減少的機會了。這樣O2就永遠存在了,O2存在,那么O2的作用域鏈指向O1的指針就永遠存在了,所以O1也就永遠存了,這樣O1、O2 就再沒機會釋放了,就造成了內存泄漏。