一、JS的回收機制node
JavaScript垃圾回收的機制很簡單:找出再也不使用的變量,而後釋放掉其佔用的內存,可是這個過程不是實時的,由於其開銷比較大,因此垃圾回收系統(GC)會按照固定的時間間隔,週期性的執行。瀏覽器
到底哪一個變量是沒有用的?因此垃圾收集器必須跟蹤到底哪一個變量沒用,對於再也不有用的變量打上標記,以備未來收回其內存。用於標記的無用變量的策略可能因實現而有所區別,一般狀況下有兩種實現方式:標記清除和引用計數。引用計數不太經常使用,標記清除較爲經常使用。閉包
二、標記清除dom
js中最經常使用的垃圾回收方式就是標記清除。當變量進入環境時,例如,在函數中聲明一個變量,就將這個變量標記爲「進入環境」。從邏輯上講,永遠不能釋放進入環境的變量所佔用的內存,由於只要執行流進入相應的環境,就可能會用到它們。而當變量離開環境時,則將其標記爲「離開環境」。函數
function test(){ var a=10;//被標記,進入環境 var b=20;//被標記,進入環境 } test();//執行完畢以後a、b又被標記離開環境,被回收
三、引用此時工具
引用計數的含義是跟蹤記錄每一個值被引用的次數。當聲明瞭一個變量並將一個引用類型值(function object array)賦給該變量時,則這個值的引用次數就是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 }
四、哪些操做會形成內存泄露url
1)意外的全局變量引發的內存泄露spa
function leak(){ leak="xxx";//leak成爲一個全局變量,不會被回收 }
2)閉包引發的內存泄露調試
function bindEvent(){ var obj=document.createElement("XXX"); obj.onclick=function(){ //Even if it's a empty function } }
閉包能夠維持函數內局部變量,使其得不到釋放。 上例定義事件回調時,因爲是函數內定義函數,而且內部函數--事件回調的引用外暴了,造成了閉包。
解決之道,將事件處理函數定義在外部,解除閉包,或者在定義事件處理函數的外部函數中,刪除對dom的引用。
//將事件處理函數定義在外部 function onclickHandler(){ //do something } function bindEvent(){ var obj=document.createElement("XXX"); obj.onclick=onclickHandler; }
3)沒有清理的DOM元素引用
var elements={ button: document.getElementById("button"), image: document.getElementById("image"), text: document.getElementById("text") }; function doStuff(){ image.src="http://some.url/image"; button.click(): console.log(text.innerHTML) } function removeButton(){ document.body.removeChild(document.getElementById('button')) }
4)被遺忘的定時器或者回調
var someResouce=getData(); setInterval(function(){ var node=document.getElementById('Node'); if(node){ node.innerHTML=JSON.stringify(someResouce) } },1000)
這樣的代碼很常見, 若是 id 爲 Node 的元素從 DOM 中移除, 該定時器仍會存在, 同時, 由於回調函數中包含對 someResource 的引用, 定時器外面的 someResource 也不會被釋放。
5)子元素存在引發的內存泄露
黃色是指直接被 js變量所引用,在內存裏,紅色是指間接被 js變量所引用,如上圖,refB 被 refA 間接引用,致使即便 refB 變量被清空,也是不會被回收的子元素 refB 因爲 parentNode 的間接引用,只要它不被刪除,它全部的父元素(圖中紅色部分)都不會被刪除。
6)IE7/8引用計數使用循環引用產生的問題
function fn(){ var a={}; var b={}; a.pro=b; b.pro=a; } fn();
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有一個名爲e的屬性指向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的回收方式已經改變
五、如何分析內存的使用狀況
Google Chrome瀏覽器提供了很是強大的JS調試工具,Memory 視圖 profiles 視圖讓你能夠對 JavaScript 代碼運行時的內存進行快照,而且能夠比較這些內存快照。它還讓你能夠記錄一段時間內的內存分配狀況。在每個結果視圖中均可以展現不一樣類型的列表,可是對咱們最有用的是 summary 列表和 comparison 列表。 summary 視圖提供了不一樣類型的分配對象以及它們的合計大小:shallow size (一個特定類型的全部對象的總和)和 retained size (shallow size 加上保留此對象的其它對象的大小)。distance 顯示了對象到達 GC 根(校者注:最初引用的那塊內存,具體內容可自行搜索該術語)的最短距離。 comparison 視圖提供了一樣的信息可是容許對比不一樣的快照。這對於找到泄漏頗有幫助。
六、怎樣避免內存泄露
1)減小沒必要要的全局變量,或者生命週期較長的對象,及時對無用的數據進行垃圾回收;
2)注意程序邏輯,避免「死循環」之類的 ;
3)避免建立過多的對象 原則:不用了的東西要及時歸還。