關於這方面的文章太多,綜合一下寫個簡單的方便自己理解,針對js的內存泄漏
1、什麼是內存泄漏?
內存泄露是指一塊被分配的內存既不能使用,又不能回收,直到瀏覽器進程結束。
2、出現情況
(1) 當頁面中元素被移除或替換時,若元素綁定的事件仍沒被移除,在IE中不會作出恰當處理,此時要先手工移除事件,不然會存在內存泄露。
<div id="myDiv">
<input type="button" value="Click me" id="myBtn">
</div>
<script type="text/javascript">
var btn = document.getElementById("myBtn");
btn.onclick = function(){
document.getElementById("myDiv").innerHTML = "Processing...";
}
</script>
上面div裏的input節點被替換了,但因爲input綁定了click事件,所以ie並不會回收內存。所以要手動去除click事件。
<script type="text/javascript">
var btn = document.getElementById("myBtn");
btn.onclick = function(){
btn.onclick = null;//增加了這句
document.getElementById("myDiv").innerHTML = "Processing...";
}
</script>
或者採用事件委託,把click事件綁定到父級
<div id="myDiv">
<input type="button" value="Click me" id="myBtn">
</div>
<script type="text/javascript">
document.onclick = function(event){
event = event || window.event;
if(event.target.id == "myBtn"){
document.getElementById("myDiv").innerHTML = "Processing...";
}
}
</script>
(2)IE7/8 DOM對象或者ActiveX對象循環引用導致內存泄漏
var a=document.getElementById("xx");
var b=document.getElementById("xxx");
a.r=b;
b.r=a;
如上述a,b,若只是他倆相互引用,並沒有被其他對象引用,那麼仍然會被垃圾收集系統識別並處理。但在IE中,因爲循環引用中的任何對象(a、b)是 DOM 節點(或者 ActiveX 對象),垃圾收集系統則不會發現它們之間的循環關係與系統中的其他對象是隔離的並釋放它們。最終它們將被保留在內存中,直到瀏覽器關閉。
(3)閉包有時也會產生內存泄漏。解決方法是將閉包處理函數放到外面去。如:
var leaks = (function(){
var leak = 'xxxxxx';// 被閉包所引用,不會被回收
return function(){
console.log(leak);
}
})()
或者這種閉包:
function bindEvent()
{
var obj=document.createElement("XXX");
obj.οnclick=onclickHandler; //這裏將onclickHandler函數放到外面去了,本來這裏是可以直接用匿名函數執行的
}
function onclickHandler(){
//do something
}
還有一種解決方法,就是在定義事件處理函數的外部函數中,刪除對dom的引用
function bindEvent()
{
var obj=document.createElement("XXX");
obj.οnclick=function(){
//Even if it's a empty function
}
obj=null; //這句是增加的解決內存泄漏的方法。
}
(4) 自動類型裝箱轉換也會造成內存泄漏
var s=”lalala”;
alert(s.length);
s本身是一個string而非object,它沒有length屬性,所以當訪問length時,JS引擎會自動創建一個臨時String對象封裝s,而這個對象一定會泄露。解決起來相當容易,記得所有值類型做.運算之前先顯式轉換一下:
var s="lalala";
alert(new String(s).length);
(5)全局變量引起的
function leaks(){
leak = 'xxxxxx';//leak 成爲一個全局變量,不會被回收
}
(6)DOM引起的內存泄漏
當原有的DOM被移除時,子結點引用沒有被移除則無法回收
var treeRef = select('#tree');
var leafRef = select('#leaf'); //在COM樹中leafRef是treeFre的一個子結點
select('body').removeChild(treeRef);//#tree不能被回收入,因爲treeRef還在
解決方法是先移除子節點
treeRef = null;//tree還不能被回收,因爲葉子結果leafRef還在
leafRef = null;//現在#tree可以被釋放了
還有一種情況,就是當動態創建的2 個不同範圍的 DOM 對象附加到一起的時候,一個臨時的對象會被創建。這個 DOM 對象改變範圍到 document 時,那個臨時對象就沒用了,這個臨時對象沒有被回收將導致內存泄漏。如果我們一一將這兩個DOM添加到原有的DOM 對象上就不會產生中間臨時對象。
另外一種,則是給DOM對象添加的屬性是一個對象的引用,也會引起內存泄漏。
var testObject = {};
document.getElementById('idname').property = testObject; //如果DOM不被消除,則testObject會一直存在,造成內存泄漏
需手動清除屬性document.getElementById('idname').property = null; //釋放內存
(7)timer定時器
setTimeout 的第一個參數使用字符串而非函數的話,會引發內存泄漏。
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 buggyObject = null;
這句不會成功。
解決辦法,先停止timer然後再回收
//解決方法,先停止定時器
clearTimeout(val);
buggyObject = null;
(8)Delete一個Object的屬性會讓此對象變慢(多耗費15倍的內存)
var o = { x: 'y' };
delete o.x; //此時o會成一個慢對象
var o = { x: 'y' };
o = null; //應該這樣
3、查看內存
Chrome自帶的內存調試工具可以很方便地查看內存使用情況和內存泄露:
在 Timeline -> Memory 點擊record即可: