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对象。所以当前三个对象都可以通过某个引用到达,所以此三个对象都不会被回收。
如果某一个主人因为某此原因不能再收养宠物时
// 放弃宠物狗 host.removePet("xiaoGou"); // 放弃宠物猫 host.removePet("xiaoMao"); host = null; cat = null;
Host使用removePet方法来放弃宠物,并在此方法中调用了pet.remove方法,使宠物也忘记自己的主人。但是由于Dog对象重写了remove方法,所以Dog并没有忘记自己的主人,所以当前的内存情况如下所示:
中于没有把dog引用设置为null,所以通过dog引用还可以到达Dog对象,而又因为Dog对象的host属性还指向Host对象,所以Host对象也是可达的,所以Dog与Host对象都不能被回收。最后把dog引用也设置为null,这时因为Dog对象不可达,所以Dog与Host对象都会被回收。
dog = null;
由此可见对于“可达性”的判断是指某一个对象是否能从JavaScript执行环境中的某引用出发而引用到此对象。
DOM节点与JavaScript对象之间引用导致不可回收的情况。
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节点才会被回收。
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)