js中的内存回收
在js中,垃圾回收器每隔一段时间就会找出那些不再使用的数据,并释放其所占用的内存空间。
以全局变量和局部变量来说,函数中的局部变量在函数执行结束后这些变量已经不再被需要,所以垃圾回收器会识别并释放它们。而对于全局变量,垃圾回收器很难判断这些变量什么时候才不被需要,所以尽量少使用全局变量。
2.垃圾回收的两种模式
那么垃圾回收器是如何检测变量是否需要的呢,大体上分为两种检测手段,引用计数与标记清除。
引用计数
引用计数的判断原理很简单,就是看一份数据是否还有指向它的引用,如果没有任何对象再指向它,那么垃圾回收器就会回收
引用计数存在一个很大的问题,就是对象间的循环引用,比如如下代码中,对象o1与o2相互引用,即便函数执行完毕,垃圾回收器通过引用计数也无法释放它们。
function f() { var o1 = {}; var o2 = {}; o1.a = o2; // o1 引用 o2 o2.a = o1; // o2 引用 o1 return; }; f();
而且循环引用对象序列化的时候会发生问题,比如调用JSON.stringify()对该类对象进行序列化,就会报错: Converting circular structure to JSON. 而序列化需求很常见,比如发起一个ajax请求提交一个对象就需要对对象进行序列化。
针对这种问题的一种解决方案是去除这种循环引用,Douglas Crockford写了一个JSON的扩展包,里面的cycle.js刚好就是用来解决此类问题。
例如:
var c = JSON.decycle(a)
这样就去除了a对象的循环引用,如果想恢复原本的循环引用对象a,可以调用retrocycle方法
var a = JSON.retrocycle(c)
标记清除
标记清除的概念也好理解,从根部出发看是否能达到某个对象,如果能达到则认定这个对象还被需要,如果无法达到,则释放它,这个过程大致分为三步:
a.垃圾回收器创建roots列表,roots通常是代码中保留引用的全局变量,在js中,我们一般认定全局对象window作为root,也就是所谓的根部。
b.从根部出发检查所有 的roots,所有的children也会被递归检查,能从root到达的都会被标记为active。
c.未被标记为active的数据被认定为不再需要,垃圾回收器开始释放它们。
当一个对象零引用时,我们从根部一定无法到达;但反过来,从根部无法到达的不一定是严格意义上的零引用,比如循环引用,所以标记清除要更优于引用计数。
从2012年起,所有现代浏览器都使用了标记清除垃圾回收算法,但老版本的IE6除外。