闭包是一个非常强大的特性,但人们对其也有诸多无解。一种危言耸听的说法是闭包会造成内存泄露。
局部变量本来应该在函数退出的时候被解除引用,但如果局部变量被封闭在闭包形成的环境中,那么这个局部变量就能一直生存下去。从这个意义上看,闭包的确会使一些数据无法被及时销毁。使用闭包的一部分原因是我们选择主动把一些变量封存在闭包中,因为可能在以后还需要使用这些变量,把这些变量放在闭包中和放在全局作用域,对内存方面的影响是一致的,这里并不能说成是内存泄露。如果在将来需要回收这些变量,我们可以手动把这些变量设为null。
跟闭包和内存泄露有关系的地方是,使用闭包的同时比较容易形成循环引用,如果闭包的作用域链中保存着一些DOM节点,这时候就有可能造成内存泄露。但这本身并非闭包的问题,也并非JavaScript的问题。在IE浏览器中,由于BOM和DOM中的对象是使用C++以COM对象的方式实现的,而COM对象的垃圾收集机制采用的是引用计数策略。在基于引用计数策略的垃圾回收机制中,如果两个对象之间形成了循环引用,那么这两个对象都无法被回收,但循环引用造成的内存泄露在本质上也不是闭包造成的。
Javascript 的垃圾回收机制,我现在知道的有两种:标记、计数。
标记清除:主流策略,并且与此问题无关。
引用计数:容易在循环引用时出现问题的策略。因为计数记录的是被引用的次数,所以循环引用时计数并不会消除。导致无法释放内存。IE 9 - 的问题是在环境中bom和dom不是原生的js对象,而是com对象,而com对象的垃圾收集机制是引用计数策略。换句话说,只要ie中存在着com对象,就会存在循环引用的问题。比如
var element=document.getElementById("someElement");
var myobject=new Object();
myobject.element=element;
element.someObject=myobject;
这个例子中js对象和dom对象之间建立了循环引用,由于存在这个循环引用,即使将com对象从页面移除,也永远不会被回收。为避免类似问题,应该在不使用它们的时候手动把js对象和com对象断开。
myobject.element=null;
element.someObject=null;
ie9之前的浏览器对javascript对象和com对象使用不用的垃圾收集机制,因此闭包在ie的这些版本中会导致一些特殊的问题
比如
function assignHandler(){
var element=document.getElementById("someElement");
element.onclick=function(){
alert(element.Id);
}
}
以上代码创建了一个作为element元素事件处理程序的闭包,这个闭包又创建了一个循环引用,由于匿名函数保存了一个对assignHandler()的活动对象的引用,因此就会导致无法减少element的引用数,因此,只要匿名函数存在,element的引用至少是1,因此所占的内存就无法回收,要改下才能解决
function assignHandler(){
var element=document.getElementById("someElement");
var id=element.Id;
element.onclick=function(){
alert(id);
};
element=null;
}
通过把element.Id的副本保存在本地的一个变量中,并且在闭包中引用改变量消除循环引用,但仅仅做到这一步,还不能消除内存泄漏,因为闭包会引用包含函数的整个活动对象,而其中包含着element;即使不直接引用,包含函数的活动对象中仍然保存着一个引用,因此有必要把element变量设置为null。
同样,如果要解决循环引用带来的内存泄露问题,我们只需要把循环引用中的变量设为null即可。将变量设置为null意味着切断变量与它此前引用的值之间的联系。当垃圾收集器下次运行时,就会删除这些值并回收他们占用的内存。