JavaScript具有自动垃圾回收机制,执行环境会负责管理代码执行过程中使用的内存。
垃圾收集器必须跟踪哪个变量有用,对于不再有用的变量打上标记,以备将来收回其占用的内存。用于标识无用变量的策略因实现而异,但具体到浏览器中的实现,通常有两个策略。
1、标记清除
JavaScript中最常用的垃圾收集方式是标记清除(mark-and-sweep)。当变量进入环境时,就将这个变量标记为“进入环境”。从逻辑上讲,永远不能释放进入环境的变量所占用的内存,因为只要执行流进入相应的环境,就可能会用到它们。而当变量离开环境时,则将其标记为“离开环境”。
垃圾收集器在运行的时候会给存储在内存中的所有变量都加上标记(当然,可以使用任何标记方式)。然后,它会去掉环境中的变量以及被环境中的变量引用的变量的标记。而在此之后再被加上标记的变量将被视为准备删除的变量,原因是环境中的变量已经无法访问到这些变量了。最后,垃圾收集器完成内存清除工作,销毁那些带标记的值并回收他们所占用的内存空间。
2、引用计数
另一种不太常用的垃圾收集策略叫做引用计数(reference counting)。引用计数的含义是跟踪记录每个值被引用的次数。当声明了一个变量并将一个引用类型值赋给该变量时,则这个值的引用次数就是1.如果同一个值又被赋给另一个变量,则该值的引用次数加1。,相反,如果包含对这个引用的变量取得了另外一个值,则这个值的引用次数减1.当这个值的引用次数变成0时,则说明没有办法再访问这个值了,因而就可以将其占用的内存空间收回来。这样当垃圾回收器下次再运行时,它就会释放那些引用次数为零的值所占用的内存。
循环引用:对象A中包含一个指向对象B的指针,而对象B中也包含一个指向对象A的引用。如下:
function problem() {
var objectA = new Object();
var objectB = new Object();
objectA.someOtherObject = objectB;
objectB.anotherObject = objectA;
}
在这个例子中,objectA 和 objectB 通过各自的属性相互引用。也就是说,这两个对象的引用次数都是2.在采用标记清除策略的实现中,由于函数执行后,这两个对象都离开了作用域,因此这种相互引用不是个问题。但在采用引用计数策略的实现中,当函数执行完毕后,objectA 和 objectB 还将继续存在,因此它们的引用次数永远不会是 0。假如这个函数被重复多次引用,将会导致大量内存得不到回收。
另外,IE中有一部分对象并不是原生 JavaScript 对象。例如,其BOM和DOM中的对象就是使用C++以COM(Component Object Model,组件对象模型)对象的形式实现的。而COM对象的垃圾收集机制采用的引用计数策略。因此即使IE的JavaScript引擎是使用标记清除策略来实现的,但JavaScript访问的COM对象依然是基于引用计数策略的。
也就是说,只要在IE中涉及COM对象,就会存在循环引用的问题。如下
var element = document.getElementById("some_element");
var myObject = new Object();
myObject.element = element;
element.someObject = myObject;
这个例子中,一个DOM元素与一个原生JavaScript对象之间创建了循环引用。其中,变量myObject有一个名为element的属性指向element对象;反之亦然。由于存在这个循环引用,即使将例子中的DOM从页面中移除,它也永远不会被回收。
为了避免类似这样的循环引用问题,最好是在不使用它们的时候手动断开原生 JavaScript对象与DOM元素之间的连接。
myObject.element = null;
element.someObject = null;