在談內存泄漏這個問題以前先看看JavaScript的垃圾收集機制,JavaScript 具備自動垃圾收集機制,就是找出那些再也不繼續使用的變量,而後釋放其佔用的內存。爲此,垃圾收集器會按照固定的時間間隔(或代碼執行中預約的收集時間)。經常使用的的方法有兩種,即標記清楚和引用計數。瀏覽器
標記清除閉包
JavaScript 中最經常使用的垃圾收集方式是標記清除(mark-and-sweep)。垃圾收集器在運行的時候會給存儲在內存中的全部變量都加上標記(可使用任何標記方式)。而後,它會去掉環境中的變量以及被環境中的變量引用的變量的標記。而在此以後再被加上標記的變量將被視爲準備刪除的變量,緣由是環境中的變量已經沒法訪問到這些變量了。最後,垃圾收集器完成內存清除工做,銷燬那些帶標記的值並回收它們所佔用的內存空間。函數
引用計數spa
引用計數(reference counting)的含義是跟蹤記錄每一個值被引用的次數。引用計數的含義是跟蹤記錄每一個值被引用的次數。當聲明瞭一個變量並將一個引用類型值賦給該變量時,則這個值的引用次數就是1。若是同一個值又被賦給另外一個變量,則該值的引用次數加1。相反,若是包含對這個值引用的變量又取得了另一個值,則這個值的引用次數減1。當這個值的引用次數變成0 時,則說明沒有辦法再訪問這個值了,於是就能夠將其佔用的內存空間回收回來。這樣,當垃圾收集器下次再運行時,它就會釋放那些引用次數爲零的值所佔用的內存。code
Netscape Navigator 3.0 是最先使用引用計數策略的瀏覽器,但很快它就遇到了一個嚴重的問題,請看下面這個例子:對象
1
2
3
4
5
6
|
function
problem(){
var
objectA =
new
Object();
var
objectB =
new
Object();
objectA.someOtherObject = objectB;
objectB.anotherObject = objectA;
}
|
說明:objectA 和objectB 經過各自的屬性相互引用,即這兩個對象的引用次數都是2,在採用標記清除策略的實現中,因爲函數執行以後,這兩個對象都離開了做用域,所以這種相互引用不是個問題。但在採用引用計數策略的實現中,當函數執行完畢後,objectA 和objectB 還說明將繼續存在,由於它們的引用次數永遠不會是0。假如這個函數被重複屢次調用,就會致使大量內存得不到回收。blog
爲此,Netscape 在Navigator 4.0 中放棄了引用計數方式,然而引用計數致使的麻煩並未就此告終。IE9之前中有一部分對象並非原生JavaScript 對象。例如,其BOM 和DOM 中的對象就是使用C++以COM(Component Object Model,組件對象模型)對象的形式實現的,而COM 對象的垃圾收集機制採用的就是引用計數策略。所以,即便IE 的JavaScript 引擎是使用標記清除策略來實現的,但JavaScript 訪問的COM 對象依然是基於引用計數策略的。換句話說,只要在IE 中涉及COM 對象,就會存在循環引用的問題。 好比:事件
1
2
3
4
|
var
element = document.getElementById(
"some_element"
);
var
myObject =
new
Object();
myObject.element = element;
element.someObject = myObject;
|
DOM 元素(element)與一個原生JavaScript 對象(myObject)之間建立了循環引用。其中,變量myObject 有一個名爲element 的屬性指向element 對象;而變量element 也有一個屬性名叫someObject 回指myObject。因爲存在這個循環引用,即便將例子中的DOM 從頁面中移除,它也永遠不會被回收。ip
解決辦法:將變量設爲null從而切斷變量與它此前引用的值之間的鏈接。內存
1
2
|
myObject.element =
null
;
element.someObject =
null
;
|
看完上面的內容,我來談正題。
因爲IE9 以前的版本對JScript 對象和COM 對象使用不一樣的垃圾收集。所以閉包在IE 的這些版本中會致使一些特殊的問題。具體來講,若是閉包的做用域鏈中保存着一個HTML 元素,那麼就意味着該元素將沒法被銷燬
請看例子:
1
2
3
4
5
6
|
function
assignHandler(){
var
element = document.getElementById(
"someElement"
);
element.onclick =
function
(){
alert(element.id);
};
}
|
以上代碼建立了一個做爲element 元素事件處理程序的閉包,而這個閉包則又建立了一個循環引用(事件將在第13 章討論)。因爲匿名函數保存了一個對assignHandler()的活動對象的引用,所以就會致使沒法減小element 的引用數。只要匿名函數存在,element 的引用數至少也是1,所以它所佔用的內存就永遠不會被回收
解決辦法前言已經提到過,把element.id 的一個副本保存在一個變量中,從而消除閉包中該變量的循環引用同時將element變量設爲null。
1
2
3
4
5
6
7
8
|
function
assignHandler(){
var
element = document.getElementById(
"someElement"
);
var
id = element.id;
element.onclick =
function
(){
alert(id);
};
element =
null
;
}
|
總結:閉包並不會引發內存泄漏,只是因爲IE9以前的版本對JScript對象和COM對象使用不一樣的垃圾收集,從而致使內存沒法進行回收,這是IE的問題,因此閉包和內存泄漏沒半毛錢關係。