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);
因此可知當前這個三對象在內存中的狀況以下所示: 閉包
從上圖能夠看出,在內存中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
中於沒有把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樹中。當前在內存中造成的狀況以下圖所示:
如今若是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"]節點都不能回收。具體的內存狀況以下所示:
因此要想回收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;
以後,內存的狀況以下所示:
能夠從上圖看出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)