垃圾收集

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对象。所以当前三个对象都可以通过某个引用到达,所以此三个对象都不会被回收。

如果某一个主人因为某此原因不能再收养宠物时

// 放弃宠物狗
host.removePet("xiaoGou");
// 放弃宠物猫
host.removePet("xiaoMao");
host = null;
cat = null;

Host使用removePet方法来放弃宠物,并在此方法中调用了pet.remove方法,使宠物也忘记自己的主人。但是由于Dog对象重写了remove方法,所以Dog并没有忘记自己的主人,所以当前的内存情况如下所示:

8613d779581a4900b5fc1dfa51040c0c

中于没有把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树中。当前在内存中形成的情况如下图所示:

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)

posted @ 2015-03-20 22:13  wangg_mail  阅读(170)  评论(0编辑  收藏  举报