【转】闭包会形成内存泄漏吗?

前言

  在谈内存泄漏这个问题以前先看看JavaScript的垃圾收集机制,JavaScript 具备自动垃圾收集机制,就是找出那些再也不继续使用的变量,而后释放其占用的内存。为此,垃圾收集器会按照固定的时间间隔(或代码执行中预约的收集时间)。经常使用的的方法有两种,即标记清楚和引用计数。浏览器

  标记清除闭包

  JavaScript 中最经常使用的垃圾收集方式是标记清除(mark-and-sweep)。垃圾收集器在运行的时候会给存储在内存中的全部变量都加上标记(可使用任何标记方式)。而后,它会去掉环境中的变量以及被环境中的变量引用的变量的标记。而在此以后再被加上标记的变量将被视为准备删除的变量,缘由是环境中的变量已经没法访问到这些变量了。最后,垃圾收集器完成内存清除工做,销毁那些带标记的值并回收它们所占用的内存空间。函数

  引用计数spa

  引用计数(reference counting)的含义是跟踪记录每一个值被引用的次数。引用计数的含义是跟踪记录每一个值被引用的次数。当声明了一个变量并将一个引用类型值赋给该变量时,则这个值的引用次数就是1。若是同一个值又被赋给另外一个变量,则该值的引用次数加1。相反,若是包含对这个值引用的变量又取得了另一个值,则这个值的引用次数减1。当这个值的引用次数变成0 时,则说明没有办法再访问这个值了,于是就能够将其占用的内存空间回收回来。这样,当垃圾收集器下次再运行时,它就会释放那些引用次数为零的值所占用的内存。code

  Netscape Navigator 3.0 是最先使用引用计数策略的浏览器,但很快它就遇到了一个严重的问题,请看下面这个例子:对象

1
2
3
4
5
6
function problem(){
     var objectA = new Object();
     var objectB = new Object();
     objectA.someOtherObject = objectB;
     objectB.anotherObject = objectA;
}

  说明:objectA 和objectB 经过各自的属性相互引用,即这两个对象的引用次数都是2,在采用标记清除策略的实现中,因为函数执行以后,这两个对象都离开了做用域,所以这种相互引用不是个问题。但在采用引用计数策略的实现中,当函数执行完毕后,objectA 和objectB 还说明将继续存在,由于它们的引用次数永远不会是0。假如这个函数被重复屡次调用,就会致使大量内存得不到回收。blog

  为此,Netscape 在Navigator 4.0 中放弃了引用计数方式,然而引用计数致使的麻烦并未就此告终。IE9之前中有一部分对象并非原生JavaScript 对象。例如,其BOM 和DOM 中的对象就是使用C++以COM(Component Object Model,组件对象模型)对象的形式实现的,而COM 对象的垃圾收集机制采用的就是引用计数策略。所以,即便IE 的JavaScript 引擎是使用标记清除策略来实现的,但JavaScript 访问的COM 对象依然是基于引用计数策略的。换句话说,只要在IE 中涉及COM 对象,就会存在循环引用的问题。 好比:事件

1
2
3
4
var element = document.getElementById( "some_element" );
var myObject = new Object();
myObject.element = element;
element.someObject = myObject;

  DOM 元素(element)与一个原生JavaScript 对象(myObject)之间建立了循环引用。其中,变量myObject 有一个名为element 的属性指向element 对象;而变量element 也有一个属性名叫someObject 回指myObject。因为存在这个循环引用,即便将例子中的DOM 从页面中移除,它也永远不会被回收。ip

  解决办法:将变量设为null从而切断变量与它此前引用的值之间的链接。内存

1
2
myObject.element = null ;
element.someObject = null ;

  看完上面的内容,我来谈正题。

 闭包不会引发内存泄漏

  因为IE9 以前的版本对JScript 对象和COM 对象使用不一样的垃圾收集。所以闭包在IE 的这些版本中会致使一些特殊的问题。具体来讲,若是闭包的做用域链中保存着一个HTML 元素,那么就意味着该元素将没法被销毁
请看例子:

1
2
3
4
5
6
function assignHandler(){
     var element = document.getElementById( "someElement" );
     element.onclick = function (){
         alert(element.id);
     };
}

  以上代码建立了一个做为element 元素事件处理程序的闭包,而这个闭包则又建立了一个循环引用(事件将在第13 章讨论)。因为匿名函数保存了一个对assignHandler()的活动对象的引用,所以就会致使没法减小element 的引用数。只要匿名函数存在,element 的引用数至少也是1,所以它所占用的内存就永远不会被回收

  解决办法前言已经提到过,把element.id 的一个副本保存在一个变量中,从而消除闭包中该变量的循环引用同时将element变量设为null。

1
2
3
4
5
6
7
8
function assignHandler(){
     var element = document.getElementById( "someElement" );
     var id = element.id;
     element.onclick = function (){
         alert(id);
     };
     element = null ;
}

  总结:闭包并不会引发内存泄漏,只是因为IE9以前的版本对JScript对象和COM对象使用不一样的垃圾收集,从而致使内存没法进行回收,这是IE的问题,因此闭包和内存泄漏没半毛钱关系。

相关文章
相关标签/搜索