先看題目
下列代碼存在幾個變量沒有被回收?
var i = 1;
var i = 2;
var add = function() {
var i = 0;
return function()
{
i++;
console.log(i);
}
}();
add();
答案
:3,全局變量有兩個,即i和add,局部變量有一個i,因為局部變量被另一個作用域引用,所以局部變量i也不回收
var i = 1; // 全局變量不會被回收
var i = 2; // 這里重復聲明變量i,因此var聲明被忽略,只是把i賦值為2
var add = function() { // 全局變量不會被回收
var i = 0; // 局部變量
return function() {
i++;
console.log(i); // 被另一個作用域引用導致不會被回收
}
}();
add();
變量回收原則
-
全局變量不會被回收。
-
局部變量會被回收,也就是函數一旦運行完以后,函數內部的東西都會被銷毀。
-
只要被另外一個作用域所引用就不會被回收
JS的垃圾回收機制
標記清除
JS中最常見的垃圾回收方式是標記清除
標記清除的概念也好理解,從根部出發看是否能達到某個對象,如果能達到則認定這個對象還被需要,如果無法達到,則釋放它,這個過程大致分為三步:
-
垃圾回收器創建roots列表,roots通常是代碼中保留引用的全局變量,在js中,我們一般認定全局對象window作為root,也就是所謂的根部。
-
從根部出發檢查所有 的roots,所有的children也會被遞歸檢查,能從root到達的都會被標記為active。
-
未被標記為active的數據被認定為不再需要,垃圾回收器開始釋放它們。
注意:
當一個對象零引用
時,我們從根部一定無法到達
。
但反過來,從根部無法到達
的不一定
是嚴格意義上的零引用
,比如循環引用,所以標記清除要更優於引用計數
。
引用計數
工作原理:跟蹤記錄每個值被引用的次數。
什么情況會引起內存泄漏(無法釋放已經不使用的內存)?
雖然有垃圾回收機制但是我們編寫代碼操作不當還是會造成內存泄漏。
-
意外的全局變量引起的內存泄漏。
原因:全局變量,不會被回收。
解決:使用嚴格模式避免。 -
閉包引起的內存泄漏
原因:閉包可以維持函數內局部變量,使其得不到釋放。
解決:將事件處理函數定義在外部,解除閉包,或者在定義事件處理函數的外部函數中,刪除對dom的引用。 -
沒有清理的DOM元素引用
原因:雖然別的地方刪除了,但是對象中還存在對dom的引用
解決:手動刪除。 -
被遺忘的定時器或者回調
原因:定時器中有dom的引用,即使dom刪除了,但是定時器還在,所以內存中還是有這個dom。
解決:手動刪除定時器和dom。 -
子元素存在引用引起的內存泄漏
原因:div中的ul li 得到這個div,會間接引用某個得到的li,那么此時因為div間接引用li,即使li被清空,也還是在內存中,並且只要li不被刪除,他的父元素都不會被刪除。
解決:手動刪除清空。