1、JS的回收機制
JavaScript垃圾回收的機制很簡單:找出不再使用的變量,然后釋放掉其占用的內存,但是這個過程不是實時的,因為其開銷比較大,所以垃圾回收系統(GC)會按照固定的時間間隔,周期性的執行。
到底哪個變量是沒有用的?所以垃圾收集器必須跟蹤到底哪個變量沒用,對於不再有用的變量打上標記,以備將來收回其內存。用於標記的無用變量的策略可能因實現而有所區別,通常情況下有兩種實現方式:標記清除和引用計數。引用計數不太常用,標記清除較為常用。
2、標記清除
js中最常用的垃圾回收方式就是標記清除。當變量進入環境時,例如,在函數中聲明一個變量,就將這個變量標記為“進入環境”。從邏輯上講,永遠不能釋放進入環境的變量所占用的內存,因為只要執行流進入相應的環境,就可能會用到它們。而當變量離開環境時,則將其標記為“離開環境”。
function test(){ var a= 10; //被標記,進入環境 var b= 20; //被標記,進入環境 } test(); //執行完畢之后a、b又被標記離開環境,被回收
3、引用此時
引用計數的含義是跟蹤記錄每個值被引用的次數。當聲明了一個變量並將一個引用類型值(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 }
4、哪些操作會造成內存泄露
1)意外的全局變量引起的內存泄露
function leak(){ js= "xxx"; //js成為一個全局變量,不會被回收 }
2)閉包引起的內存泄露
function bindEvent(){ var obj= document.createElement( "XXX"); obj.οnclick= 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.οnclick= function(){
//Even if it's a empty function
}
obj= null;
}
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.οnlοad= function outerFunction(){
var obj= document.getElementById( "element"):
obj.οnclick= function innerFunction(){};
obj= null;
};
將變量設置為null意味着切斷變量與它此前引用的值之間的連接。當垃圾回收器下次運行時,就會刪除這些值並回收它們占用的內存。 要注意的是,IE9+並不存在循環引用導致Dom內存泄漏問題,可能是微軟做了優化,或者Dom的回收方式已經改變
5、如何分析內存的使用情況
Google Chrome瀏覽器提供了非常強大的JS調試工具,Memory 視圖 profiles 視圖讓你可以對 JavaScript 代碼運行時的內存進行快照,並且可以比較這些內存快照。它還讓你可以記錄一段時間內的內存分配情況。在每一個結果視圖中都可以展示不同類型的列表,但是對我們最有用的是 summary 列表和 comparison 列表。 summary 視圖提供了不同類型的分配對象以及它們的合計大小:shallow size (一個特定類型的所有對象的總和)和 retained size (shallow size 加上保留此對象的其它對象的大小)。distance 顯示了對象到達 GC 根(校者注:最初引用的那塊內存,具體內容可自行搜索該術語)的最短距離。 comparison 視圖提供了同樣的信息但是允許對比不同的快照。這對於找到泄漏很有幫助。
6、怎樣避免內存泄露
1)減少不必要的全局變量,或者生命周期較長的對象,及時對無用的數據進行垃圾回收;
2)注意程序邏輯,避免“死循環”之類的 ;
內存泄漏:指一塊被分配的內存既不能使用,又不能回收,直到瀏覽器進程結束。
1、JS的回收機制
JavaScript垃圾回收的機制很簡單:找出不再使用的變量,然后釋放掉其占用的內存,但是這個過程不是實時的,因為其開銷比較大,所以垃圾回收系統(GC)會按照固定的時間間隔,周期性的執行。
到底哪個變量是沒有用的?所以垃圾收集器必須跟蹤到底哪個變量沒用,對於不再有用的變量打上標記,以備將來收回其內存。用於標記的無用變量的策略可能因實現而有所區別,通常情況下有兩種實現方式:標記清除和引用計數。引用計數不太常用,標記清除較為常用。
2、標記清除
js中最常用的垃圾回收方式就是標記清除。當變量進入環境時,例如,在函數中聲明一個變量,就將這個變量標記為“進入環境”。從邏輯上講,永遠不能釋放進入環境的變量所占用的內存,因為只要執行流進入相應的環境,就可能會用到它們。而當變量離開環境時,則將其標記為“離開環境”。
-
function test(){
-
var a= 10; //被標記,進入環境
-
var b= 20; //被標記,進入環境
-
}
-
test(); //執行完畢之后a、b又被標記離開環境,被回收
3、引用此時
引用計數的含義是跟蹤記錄每個值被引用的次數。當聲明了一個變量並將一個引用類型值(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
-
}
4、哪些操作會造成內存泄露
1)意外的全局變量引起的內存泄露
-
function leak(){
-
leak= "xxx"; //leak成為一個全局變量,不會被回收
-
}
2)閉包引起的內存泄露
-
function bindEvent(){
-
var obj= document.createElement( "XXX");
-
obj.οnclick= 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.οnclick= function(){
-
//Even if it's a empty function
-
}
-
obj= null;
-
}
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.οnlοad= function outerFunction(){
-
var obj= document.getElementById( "element"):
-
obj.οnclick= function innerFunction(){};
-
obj= null;
-
};
將變量設置為null意味着切斷變量與它此前引用的值之間的連接。當垃圾回收器下次運行時,就會刪除這些值並回收它們占用的內存。 要注意的是,IE9+並不存在循環引用導致Dom內存泄漏問題,可能是微軟做了優化,或者Dom的回收方式已經改變
5、如何分析內存的使用情況
Google Chrome瀏覽器提供了非常強大的JS調試工具,Memory 視圖 profiles 視圖讓你可以對 JavaScript 代碼運行時的內存進行快照,並且可以比較這些內存快照。它還讓你可以記錄一段時間內的內存分配情況。在每一個結果視圖中都可以展示不同類型的列表,但是對我們最有用的是 summary 列表和 comparison 列表。 summary 視圖提供了不同類型的分配對象以及它們的合計大小:shallow size (一個特定類型的所有對象的總和)和 retained size (shallow size 加上保留此對象的其它對象的大小)。distance 顯示了對象到達 GC 根(校者注:最初引用的那塊內存,具體內容可自行搜索該術語)的最短距離。 comparison 視圖提供了同樣的信息但是允許對比不同的快照。這對於找到泄漏很有幫助。
6、怎樣避免內存泄露
1)減少不必要的全局變量,或者生命周期較長的對象,及時對無用的數據進行垃圾回收;
2)注意程序邏輯,避免“死循環”之類的 ;
3)避免創建過多的對象 原則:不用了的東西要及時歸還。