定義
內存泄漏:應用程序不再需要的內存,由於某種原因,內存沒有返回到操作系統或可用內存池中。
原因
1.意外的全局變量
JS 在處理未聲明的變量時,對未聲明的變量的引用會在全局對象內創建一個新變量。這些全局變量是無法進行垃圾回收的(除非將它們賦值為 null 或重新進行分配),所以對於存儲大量數據的全局變量,確保在使用完之后,對其賦值為 null 或者重新分配。
-
function leak(){
-
leak= "xxx";//leak成為一個全局變量,不會被回收
-
}
2.被遺忘的定時器(Timers)或者回調函數(callback)
-
var someResouce=getData();
-
setInterval( function(){
-
var node=document.getElementById('Node');
-
if(node){
-
node.innerHTML= JSON.stringify(someResouce)
-
}
-
}, 1000)
-
// 如果 id 為 Node 的元素從 DOM 中移除, 該定時器仍會存在,
-
// 而且, 因為回調函數中包含對 someResource 的引用, 定時器外面的 someResource 也不會被釋放。
3.閉包函數
-
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;
-
}
-
-
-
//在定義事件處理函數的外部函數中,刪除對dom的引用
-
function bindEvent(){
-
var obj=document.createElement("XXX");
-
obj.onclick= function(){
-
//Even if it's a empty function
-
}
-
obj= null;
-
}
4.沒有清理的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'))
-
}
5.子元素存在引起的內存泄露
黃色是指直接被 js變量所引用,在內存里,紅色是指間接被 js變量所引用,如上圖,refB 被 refA 間接引用,導致即使 refB 變量被清空,也是不會被回收的。子元素 refB 由於 parentNode 的間接引用,只要它不被刪除,它所有的父元素(圖中紅色部分)都不會被刪除。
在舉個例子,假設我們在 JavaScript 代碼中保留了對 table 特定單元格(<td>
)的引用。有一天,我們決定從 DOM 中刪除該 table,但仍保留着對該單元格的引用。直觀地來看,可以假設 GC 將收集除了該單元格之外所有的內容。實際上,這是不會發生的,因為該單元格是該 table 的子節點,並且 children 保持着對它們 parents 的引用。也就是說,在 JavaScript 代碼中對單元格的引用會導致整個表都保留在內存中的。
6.IE7/8引用計數使用循環引用產生的問題
這個問題主要是在於IE7/8的垃圾回收機制,使用的是引用計數法,當兩個變量A和B相互引用時,即使應用程序中不再使用這兩個變量,GC也不會將這兩個變量回收。
-
function fn(){
-
var a={};
-
var b={};
-
a.pro=b;
-
b.pro=a;
-
}
-
fn();
排查
Google Chrome瀏覽器提供了非常強大的JS調試工具,Memory 視圖 profiles 視圖讓你可以對 JavaScript 代碼運行時的內存進行快照,並且可以比較這些內存快照。它還讓你可以記錄一段時間內的內存分配情況。在每一個結果視圖中都可以展示不同類型的列表,但是對我們最有用的是 summary 列表和 comparison 列表。 summary 視圖提供了不同類型的分配對象以及它們的合計大小:shallow size (一個特定類型的所有對象的總和)和 retained size (shallow size 加上保留此對象的其它對象的大小)。distance 顯示了對象到達 GC 根(校者注:最初引用的那塊內存,具體內容可自行搜索該術語)的最短距離。 comparison 視圖提供了同樣的信息但是允許對比不同的快照。這對於找到泄漏很有幫助。
右邊視圖中列出了heap里的對象列表。
- constructor:類名
- Distance:對象到根的引用層級距離
- Objects Count:該類的對象數
- Shallow Size:對象所占內存(不包含內部引用的其他對象所占的內存)
- Retained Size:對象所占的總內存(包含····················································)
內存泄漏的排查
將上圖框框切換到comparison(對照)選項,該視圖列出了當前視圖與上一個視圖的對象差異
- New:新建了多少對象
- Deleted:回收了多少對象
- Delta:新建的對象個數減去回收的對象個數
重點看closure(閉包),如果#Delta為正數,則表示創建了閉包函數,如果多個快照中都沒有變負數,則表示沒有銷毀閉包