垃圾收集

JavaScript基於引用計數規則自動收集垃圾。若是一個對象再也不被任何一個「引用」引用,那麼稱此對象不可達。JavaScript垃圾回收機制會在接下來的某一個時刻(沒法預知的某時刻)回收此對象。 數組

var name = "hello";
name = name.toUpperCase();
// 此時「hello」對象已沒有引用能夠到達,因此「hello」對象會在接下來的某時刻被回收

對象之間引用可達性致使不能回收的狀況以下所示: 瀏覽器

/**
 * 主人,具備多個寵物
 * @param {Type}  
 */
function Host() {
    "use strict";
    this.pets = {};
}
/**
 * 收養寵物
 * @param {string} name
 * @param {Pet} pet
 */
Host.prototype.addPet = function (name, pet) {
    "use strict";
    this.pets[name] = pet;
    pet.host = this;
};
/**
 * 由於某些緣由放棄寵物
 * @param {string} name
 */
Host.prototype.removePet = function (name) {
    "use strict";
    this.pets[name].remove();
    delete this.pets[name];
};
/**
 * 寵物
 */
function Pet() {
    "use strict";
    this.host = null;
}
/**
 * 寵物忘記本身的主人
 */
Pet.prototype.remove = function () {
    "use strict";
    this.host = null;
};
/**
 * 狗
 */
function Dog() {
    "use strict";
}
Dog.prototype = new Pet();
/**
 * 狗不會忘記本身的主人
 */
Dog.prototype.remove = function () {
    "use strict";
};
/**
 * 貓
 */
function Cat() {
    "use strict";
}
Cat.prototype = new Pet();
如今讓Host收集一隻狗與一隻貓

var host = new Host();
var dog = new Dog();
// 收養寵物狗
host.addPet("xiaoGou", dog);
var cat = new Cat();
// 收養寵物貓
host.addPet("xiaoMao", cat);

因此可知當前這個三對象在內存中的狀況以下所示: 閉包

0b2aa85471f1f4167743262a297acd8b

從上圖能夠看出,在內存中Host對象的pets數組中的元素分別指向Dog與Cat對象,而Dog與Cat對象中的host屬性都指向Host對象。因此當前三個對象均可以經過某個引用到達,因此此三個對象都不會被回收。 app

若是某一個主人由於某此緣由不能再收養寵物時 函數

// 放棄寵物狗
host.removePet("xiaoGou");
// 放棄寵物貓
host.removePet("xiaoMao");
host = null;
cat = null;

Host使用removePet方法來放棄寵物,並在此方法中調用了pet.remove方法,使寵物也忘記本身的主人。可是因爲Dog對象重寫了remove方法,因此Dog並無忘記本身的主人,因此當前的內存狀況以下所示: this

8613d779581a4900b5fc1dfa51040c0c

中於沒有把dog引用設置爲null,因此經過dog引用還能夠到達Dog對象,而又由於Dog對象的host屬性還指向Host對象,因此Host對象也是可達的,因此Dog與Host對象都不能被回收。最後把dog引用也設置爲null,這時由於Dog對象不可達,因此Dog與Host對象都會被回收。spa

dog = null;

因而可知對於「可達性」的判斷是指某一個對象是否能從JavaScript執行環境中的某引用出發而引用到此對象。 prototype

DOM節點與JavaScript對象之間引用致使不可回收的狀況。 3d

function createElem() {
    "use strict";
    var elem = document.createElement("div"); // 動態建立一個Div
    elem.id = "div_id";
    document.body.appendChild(elem); // 把Div添加doby中
}
createElem();

以上代碼向body中動態建立了一個Div,並把Div添加到了DOM樹中顯示。儘管createElem方法執行完成以後,它做用域內的變量都已不可達,可是由於Div已被添加到了DOM樹中,因此Div還存於內存中沒有被回收。一至到Div被從DOM樹中的刪除,那麼Div節點纔會被回收。 code

function deleteElem(id){
    var elem = document.getElementById(id);
    document.body.removeChild(elem);
}
deleteElem("div_id");

DOM節點也可能與JavaScript對象之間造成循環引用致使不可回收的狀況。

function Button(text) {
    var button = document.createElement('input');
    button.type = "button";
    button.value = text;
    this.className = "Button";
    this.button = button; // Button對象的button屬性指向input[type="button"]的DOM節點。
    button.self = this; // input[type="button"]的DOM節點的self屬性指向了Button對象。
}
// 添加Button對象到某個位置
Button.prototype.appendTo = function (parentElem) {
    parentElem.appendChild(this.button);
}
// 添加Button的單擊事件
Button.prototype.addClickEvent = function(func) {
    this.button.onclick = func;
}
// 移除
Button.prototype.remove = function(parentElem) {
    parentElem.removeChild(this.button);
}

使用以上代碼建立一個Button對象。

// 建立Button對象
var btn = new Button("show className");
// 爲Button添加一個單擊事件
btn.addClickEvent(function () {
    console.log( this.self.className ); // Button
});
var parentElem = document.getElementById("parent_id");
// 把button添加到DOM樹中
btn.appendTo( parentElem );

在建立了一個Button對象以後,再給它添加了一個單擊事件,並把它添加到DOM樹中。當前在內存中造成的狀況以下圖所示:

ae95e3561e966b5ab4fa8fbefd7a02bc

如今若是input[type="button"]已使用完成,把它從DOM樹中刪除。

btn.remove( parentElem );
btn = null;
parentElem = null;

以執行以上代碼以後,btn到Button對象的引用已不可達。input[type="button"]節點也從DOM樹中刪除。可是Button對象與input[type="button"]節點還不能被回收。由於DOM節點與JavaScript對象處於瀏覽器的不一樣引擎中(DOM節點處於渲染引擎,JavaScript對象處於JavaScript引擎),它們之間的相互引用就造成了循環引用,因此此時的Button對象與input[type="button"]節點都不能回收。具體的內存狀況以下所示:

dd905fd0a05ccc52f38302034b322ad1

因此要想回收Button對象與input[type="button"]節點,就要斷開它們之間的引用。修改代碼以下所示:

// 移除
Button.prototype.remove = function(parentElem) {
    this.button.self = null; // 斷開input[type="button"]到Button對象的引用
    parentElem.removeChild(this.button);
    this.button = null; // 斷開Button對象到input[type="button"]的引用
}

再調用remove方法,把input[type="button"]節點從DOM樹中移除。

btn.remove(parentElem);
btn = null;
parentElem = null;

以後,內存的狀況以下所示:

30bbd8628faa9da41f6c7f468c0a9b69

能夠從上圖看出Button對象與input[type="button"]節點之間再也不有引用關係,因此這時Button對象與input[type="button"]節點均可以被回收。

還可能由閉包引發的內存不能回收的狀況。

function outter() {
    "use strict";
    var obj = {
        name : "wanggang",
        age : 100
    };
    return function () {
        return obj;
    };
}

var func = outter();
var obj = func();
console.log(obj.name);
obj.name = "yxf";

當執行12行的outter方法時,它會返回函數。而這個函數再會引用着outter函數的中一個局部變量,因此當沒有執行13行代碼以前,雖然outter函數的生命週期已結束,可是outter的obj變量還存在於內存中,沒有回收。只有當執行完13行的代碼以後,outter函數中的obj變量纔會被回收。

(本文的部分例子來自於Ajax in Action)

相關文章
相關標籤/搜索