乐博娱乐»JavaScript»闭包会造成内存泄漏吗?

闭包会造成内存泄漏吗?

来源:yancyenough 宣布时间:2016-10-29 阅读次数:乐博

 前言

  在谈内存泄漏这个问题之前先看看JavaScript的垃圾收集机制,JavaScript 具有自动垃圾收集机制,就是找出那些不再继续使用的变量,然后释放其占用的内存。为此,垃圾收集器会凭据牢固的时间间隔(或代码执行中预定的收集时间)。常用的的要领有两种,即标志清楚和引用计数。

乐博

  标志清除

  JavaScript 中最常用的垃圾收集方式是标志清除(mark-and-sweep)。垃圾收集器在运行的时候会给存储在内存中的所有变量都加上标志(可以使用任何标志方式)。然后,它会去掉情况中的变量以及被情况中的变量引用的变量的标志。而在此之后再被加上标志的变量将被视为准备删除的变量,原因是情况中的变量已经无法会见到这些变量了。最后,垃圾收集器完成内存清除事情,销毁那些带标志的值并接纳它们所占用的内存空间。

  引用计数

  引用计数(reference counting)的寄义是跟踪纪录每个值被引用的次数。引用计数的寄义是跟踪纪录每个值被引用的次数。当声明了一个变量并将一个引用类型值赋给该变量时,则这个值的引用次数就是1。如果同一个值又被赋给另一个变量,则该值的引用次数加1。相反,如果包罗对这个值引用的变量又取得了另外一个值,则这个值的引用次数减1。当这个值的引用次数酿成0 时,则说明没有措施再会见这个值了,因而就可以将其占用的内存空间接纳回来。这样,当垃圾收集器下次再运行时,它就会释放那些引用次数为零的值所占用的内存。

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

function problem(){
    var objectA = new Object();
    var objectB = new Object();
    objectA.someOtherObject = objectB;
    objectB.anotherObject = objectA;
}

  说明:objectA 和objectB 通过各自的属性相互引用,即这两个工具的引用次数都是2,在接纳标志清除战略的实现中,由于函数执行之后,这两个工具都离开了作用域,因此这种相互引用不是个问题。但在接纳引用计数战略的实现中,当函数执行完毕后,objectA 和objectB 还说明将继续存在,因为它们的引用次数永远不会是0。如果这个函数被重复多次调用,就会导致大量内存得不到接纳。

  为此,Netscape 在Navigator 4.0 中放弃了引用计数方式,然而引用计数导致的麻烦并未就此了结。IE9以前中有一部门工具并不是原生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 元素(element)与一个原生JavaScript 工具(myObject)之间创建了循环引用。其中,变量myObject 有一个名为element 的属性指向element 工具;而变量element 也有一个属性名叫someObject 回指myObject。由于存在这个循环引用,纵然将例子中的DOM 从页面中移除,它也永远不会被接纳。

  解决措施:将变量设为null从而切断变量与它此前引用的值之间的连接。

myObject.element = null;
element.someObject = null;

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

 闭包不会引起内存泄漏

  由于IE9 之前的版本对JScript 工具和COM 工具使用差异的垃圾收集。因此闭包在IE 的这些版本中会导致一些特殊的问题。具体来说,如果闭包的作用域链中生存着一个HTML 元素,那么就意味着该元素将无法被销毁
请看例子:

function assignHandler(){
    var element = document.getElementById("someElement");
    element.onclick = function(){
        alert(element.id);
    };
}

  以上代码创建了一个作为element 元素事件处置惩罚程序的闭包,而这个闭包则又创建了一个循环引用(事件将在第13 章讨论)。由于匿名函数生存了一个对assignHandler()的运动工具的引用,因此就会导致无法淘汰element 的引用数。只要匿名函数存在,element 的引用数至少也是1,因此它所占用的内存就永远不会被接纳

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

function assignHandler(){
    var element = document.getElementById("someElement");
    var id = element.id;
    element.onclick = function(){
        alert(id);
    };
    element = null;
}

  总结:闭包并不会引起内存泄漏,只是由于IE9之前的版本对JScript工具和COM工具使用差异的垃圾收集,从而导致内存无法进行接纳,这是IE的问题,所以闭包和内存泄漏没半毛钱关系。