1.IE7/8 DOM對象或者ActiveX對象循環引用導致內存泄漏
循環引用分為兩種:
第一種:多個對象循環引用
var a=new Object; var b=new Object; a.r=b; b.r=a;
第二種:循環引用自己
var a=new Object; a.r=a;
對於ECMAScript 對象而言,只要沒有其他對象引用對象 a、b,也就是說它們只是相互之間的引用,那么仍然會被垃圾收集系統識別並處理。
但是,在 IE7、IE8 中,如果循環引用中的任何對象是 DOM 節點或者 ActiveX 對象,比如var a = document.getElementById("#a"),垃圾收集系統則不會發現它們之間的循環關系,因為IE的DOM回收機制和JS回收機制不是同一個。js回收機制分兩種:標記清除和引用計數,引用計數對循環引用的垃圾回收會出現內存泄漏,而IE的DOM回收機制便是采用引用計數的。IE9+並不存在循環引用導致Dom內存泄露問題,可能是微軟做了優化,或者Dom的回收方式已經改變。
下面摘自小蘋果的跟我學習javascript的垃圾回收機制與內存管理
二、標記清除 js中最常用的垃圾回收方式就是標記清除。當變量進入環境時,例如,在函數中聲明一個變量,就將這個變量標記為“進入環境”。從邏輯上講,永遠不能釋放進入環境的變量所占用的內存,因為只要執行流進入相應的環境,就可能會用到它們。而當變量離開環境時,則將其標記為“離開環境”。 function test(){ var a = 10 ; //被標記 ,進入環境 var b = 20 ; //被標記 ,進入環境 } test(); //執行完畢 之后 a、b又被標離開環境,被回收。 垃圾回收器在運行的時候會給存儲在內存中的所有變量都加上標記(當然,可以使用任何標記方式)。然后,它會去掉環境中的變量以及被環境中的變量引用的變量的標記(閉包)。而在此之后再被加上標記的變量將被視為准備刪除的變量,原因是環境中的變量已經無法訪問到這些變量了。最后,垃圾回收器完成內存清除工作,銷毀那些帶標記的值並回收它們所占用的內存空間。 到目前為止,IE、Firefox、Opera、Chrome、Safari的js實現使用的都是標記清除的垃圾回收策略或類似的策略,只不過垃圾收集的時間間隔互不相同。
三、引用計數 引用計數的含義是跟蹤記錄每個值被引用的次數。當聲明了一個變量並將一個引用類型值賦給該變量時,則這個值的引用次數就是1。如果同一個值又被賦給另一個變量,則該值的引用次數加1。相反,如果包含對這個值引用的變量又取得了另外一個值,則這個值的引用次數減1。當這個值的引用次數變成0時,則說明沒有辦法再訪問這個值了,因而就可以將其占用的內存空間回收回來。這樣,當垃圾回收器下次再運行時,它就會釋放那些引用次數為0的值所占用的內存。 function test(){ var a = {} ; //a的引用次數為0 var b = a ; //a的引用次數加1,為1 var c =a; //a的引用次數再加1,為2 var b ={}; //a的引用次數減1,為1 } Netscape Navigator3是最早使用引用計數策略的瀏覽器,但很快它就遇到一個嚴重的問題:循環引用。循環引用指的是對象A中包含一個指向對象B的指針,而對象B中也包含一個指向對象A的引用。 function fn() { var a = {}; var b = {}; a.pro = b; b.pro = a; } fn(); 以上代碼a和b的引用次數都是2,fn()執行完畢后,兩個對象都已經離開環境,在標記清除方式下是沒有問題的,但是在引用計數策略下,因為a和b的引用次數不為0,所以不會被垃圾回收器回收內存,如果fn函數被大量調用,就會造成內存泄露。在IE7與IE8上,內存直線上升。 我們知道,IE中有一部分對象並不是原生js對象。例如,其內存泄露DOM和BOM中的對象就是使用C++以COM對象的形式實現的,而COM對象的垃圾回收機制采用的就是引用計數策略。因此,即使IE的js引擎采用標記清除策略來實現,但js訪問的COM對象依然是基於引用計數策略的。換句話說,只要在IE中涉及COM對象,就會存在循環引用的問題。 var element = document.getElementById("some_element"); var myObject = new Object(); myObject.e = element; element.o = myObject; 這個例子在一個DOM元素(element)與一個原生js對象(myObject)之間創建了循環引用。其中,變量myObject有一個名為element的屬性指向element對象;而變量element也有一個屬性名為o回指myObject。由於存在這個循環引用,即使例子中的DOM從頁面中移除,它也永遠不會被回收。 看上面的例子,有同學回覺得太弱了,誰會做這樣無聊的事情,其實我們是不是就在做 window.onload=function outerFunction(){ var obj = document.getElementById("element"); obj.onclick=function innerFunction(){}; }; 這段代碼看起來沒什么問題,但是obj引用了document.getElementById(“element”),而document.getElementById(“element”)的onclick方法會引用外部環境中的變量,自然也包括obj,是不是很隱蔽啊。 解決辦法 最簡單的方式就是自己手工解除循環引用,比如剛才的函數可以這樣 myObject.element = null; element.o = null; window.onload=function outerFunction(){ var obj = document.getElementById("element"); obj.onclick=function innerFunction(){}; obj=null; }; 將變量設置為null意味着切斷變量與它此前引用的值之間的連接。當垃圾回收器下次運行時,就會刪除這些值並回收它們占用的內存。 要注意的是,IE9+並不存在循環引用導致Dom內存泄露問題,可能是微軟做了優化,或者Dom的回收方式已經改變
在上面描述的循環引用例子
window.onload=function outerFunction(){ var obj = document.getElementById("element"); obj.onclick=function innerFunction(){}; };
還可以理解成閉包循環引用導致的內存泄漏。怎么理解?
首先obj是外部的一個對象, obj.onclick定義的這個函數隱式的調用到了obj這個對象(obj.onclick函數中的this就是對象obj)。然后我們需要知道obj.onclick實際上是一個outerFunction外部的函數,為什么?DOM監聽事件不可能是局部作用域的,是全局作用域的,明白了吧。所以DOM觸發這個事件相當於是在函數outerFunction外部調用了obj.click(),而事件內部使用了outerFunction的變量obj,這就形成了一個閉包。IE7/IE8 DOM的引用計數永遠無法回收這個DOM對象。無論如何,這都是DOM循環引用導致的內存泄漏,普通閉包是不會導致內存泄漏的。
改成如下結構
window.onload=function outerFunction(){ var obj = document.getElementById("element"); $(obj).click(function innerFunction(){}); };
jQuery綁定事件最終都沒有直接綁定到DOM對象上,而是使用jQuery緩存來綁定的。詳見jQuery事件體系結構
即使此時仍然會創建一個閉包,並且也會導致同前面一樣的循環,但這里的代碼卻不會使 IE 發生內存泄漏。由於jQuery考慮到了內存泄漏的潛在危害,所以它會手動釋放自己指定的所有事件處理程序(jQuery源代碼$.fn.remove函數中有對節點的緩存釋放的處理)。只要堅持使用jQuery的事件綁定方法,就無需為這種特定的常見原因導致的內存泄漏而擔心。
但是,這並不意味着我們完全脫離了險境。當對DOM元素進行其他操作時,仍然要處處留心。只要是將JavaScript對象指定給DOM元素,就可能在舊版本IE中導致內存泄漏。jQuery只是有助於減少發生這種情況的可能性。
有鑒於此,jQuery為我們提供了另一個避免這種泄漏的工具。用.data()方法,將信息附加到DOM元素。由於這里的數據並非直接保存在擴展屬性中(jQuery使用一個內部對象並通過它創建的ID來保存這里所說的數據),因此永遠也不會構成引用循環,從而有效回避了內存泄漏問題。這種方式也就是jQuery事件綁定使用的方式。
今天學習《了不起的node.js》一書,看到里面有閉包的描述,發現和之前理解的不一樣,然后又搜索資料學習了一下。然后更堅定了我的理解,
簡單來說,閉包是執行了a函數以后,正常來說a函數的局部變量等應該能釋放,但是由於閉包導致可以繼續維持a函數內部的變量不被釋放。舉個例子
function a(){ var i=0; function b(){ alert(++i); } return b; } var c=a();//執行完成以后a函數里面的i變量不能被釋放,因為有閉包的維持。當函數a的內部函數b被函數a外的一個變量引用的時候,就創建了一個閉包。 c();
======》寫於2017.10.25
2.基礎的DOM泄漏
當原有的DOM被移除時,子結點引用沒有被移除則無法回收。
var select = document.querySelector; var treeRef = select('#tree'); var leafRef = select('#leaf'); //在COM樹中leafRef是treeFre的一個子結點 select('body').removeChild(treeRef);//#tree不能被回收入,因為treeRef還在
解決方法:
treeRef = null;//tree還不能被回收,因為葉子結果leafRef還在 leafRef = null;//現在#tree可以被釋放了
DOM 插入順序導致內存泄漏
當 動態創建的2 個不同范圍的 DOM 對象附加到一起的時候,一個臨時的對象會被創建。這個 DOM 對象改變范圍到 document 時,那個臨時對象就沒用了,這個臨時對象沒有被回收將導致內存泄漏。如果我們一一將這兩個DOM添加到原有的DOM 對象上就不會產生中間臨時對象。詳見理解和解決IE內存泄漏的頁面交叉泄露 Cross-Page Leaks。
3.timer定時器泄漏
var val = 0; for (var i = 0; i < 90000; i++) { var buggyObject = { callAgain: function() { var ref = this; val = setTimeout(function() { ref.callAgain(); }, 90000); } } buggyObject.callAgain();
這個時候你無法回收buggyObject
//雖然你想回收但是timer還在 buggyObject = null;
解決辦法,先停止timer然后再回收
//解決方法,先停止定時器 clearTimeout(val); buggyObject = null;
推薦內存泄漏文章:
理解和解決IE內存泄漏(中文翻譯):http://www.tuicool.com/articles/2AZ3y2
理解和解決IE內存泄漏(英文原版):https://msdn.microsoft.com/en-us/library/bb250448.aspx
js內存泄露的幾種情況:http://blog.csdn.net/li2274221/article/details/25217297