闭包会造成内存泄漏吗?

前言

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

1. 标记清除

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

1. 引用计数

引用计数(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,因此它所占用的内存就永远不会被回收,这是IE的问题,所以闭包和内存泄漏没半毛钱关系。

解决办法前言已经提到过,把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对象使用不同的垃圾收集,从而导致内存无法进行回收。
这篇文章里做了详细的测试,有兴趣的可以点击查看

小小插曲:发个群链接,有兴趣的可以加入交流,群号:519875573

时间: 2024-10-29 19:11:11

闭包会造成内存泄漏吗?的相关文章

闭包会造成内存泄漏吗

闭包会造成内存泄漏吗? 来源:yan 前言 在谈内存泄漏这个问题之前先看看JavaScript的垃圾收集机制,JavaScript 具有自动垃圾收集机制,就是找出那些不再继续使用的变量,然后释放其占用的内存.为此,垃圾收集器会按照固定的时间间隔(或代码执行中预定的收集时间).常用的的方法有两种,即标记清楚和引用计数. 标记清除 JavaScript 中最常用的垃圾收集方式是标记清除(mark-and-sweep).垃圾收集器在运行的时候会给存储在内存中的所有变量都加上标记(可以使用任何标记方式)

前端开发中javascript闭包会引发内存泄漏么?

本文主要是和大家一起来讨论下javascript的闭包会造成内存泄漏吗?希望通过本文的分享对大家学习javascript有所帮助.  在谈内存泄漏这个问题之前先看看JavaScript的垃圾收集机制,JavaScript 具有自动垃圾收集机制,就是找出那些不再继续使用的变量,然后释放其占用的内存.为此,垃圾收集器会按照固定的时间间隔(或代码执行中预定的收集时间).常用的的方法有两种,即标记清楚和引用计数.  标记清除  JavaScript 中最常用的垃圾收集方式是标记清除(mark-and-s

闭包中的 内存泄漏

内存泄漏 如果闭包的作用域链中保存着一个HTML元素,那么就意味着该元素将无法被销毁. 1 function assignHandler(){ 2 var element = document.getElementById("someElement"); 3 element.onclick = function(){ 4 alert(element.id); 5 } 6 } 而这个闭包则又创建另一个循环引用.由于匿名函数保存了一个对 assignHandler()的活动对象的引用,因此

内存泄漏的情况

内存泄露是指一块被分配的内存既不能使用,又不能回收,直到浏览器进程结束.浏览器中也是采用自动垃圾回收方法管理内存,但由于浏览器垃圾回收方法有bug,会产生内存泄露.虽然JavaScript 会自动垃圾收集,但是如果我们的代码写法不当,会让变量一直处于"进入环境"的状态,无法被回收.下面列一下内存泄露常见的几种情况. 全局变量引起的内存泄漏 function leaks(){ leak = 'xxxxxx';//leak 成为一个全局变量,不会被回收 } 123 闭包引起的内存泄漏 va

JS中4种常见的内存泄漏

一.什么是内存泄漏 本质上讲,内存泄漏是当一块内存不再被应用程序使用的时候,由于某种原因,这块内存没有返还给操作系统或空闲内存池的现象. 二.几种常见的内存泄漏 1.意外的全局变量 一个未声明变量的引用会在全局对象中创建一个新的变量.在浏览器的环境下,全局对象就是window,也就是说: function foo(arg) { bar = "this is a hidden global variable"; } 实际上是: function foo(arg) { window.bar

内存泄漏问题总结

1.内存的生命周期 无论你使用那种语言,内存的生命周期基本是都差不多:分配内存 —— 使用内存 —— 释放内存,以下是生命周期中每一步发生了什么的一个概述: Allocate memory —— 操作系统分配内存,允许你的程序使用它.在基础语言中(例如 C ),这是一个开发者自己处理的明确操作.然而,在高级语言中,它已经为你处理了. Use memory —— 现在你就可以使用之前分配好的内存了.当你在代码中使用变量时,读 和 写 的操作正在发生. Release memory —— 现在该释放

JavaScript的闭包和内存泄漏问题

闭包 JavaScript中必须提到的功能最强大的抽象概念之一:闭包.它究竟是做什么的呢? 1 function makeAdder(a) { 2 return function(b) { 3 return a + b; 4 } 5 } 6 var x = makeAdder(5); 7 var y = makeAdder(20); 8 x(6); // 11 9 y(7); // 27 makeAdder这个名字本身应该能说明函数是用来做什么的:他创建了一个新的adder函数,这个函数自身带有

闭包和内存泄漏

由于IE9之前的版本对JScript对象和COM对象使用不同的垃圾收集例程,因此闭包在IE的这些版本中会导致一些特殊的问题.具体来说,如果闭包的作用域链中保存着一个HTML元素,那么久意味着该元素将无法被销毁.来看下面的例子 function assignHandler(){ var element=document.getElementById("someElement"); element.onclick=function(){ alert(element.id); }; } 以上

关于js闭包是否真的会造成内存泄漏(转载)

闭包是一个非常强大的特性,但人们对其也有诸多无解.一种危言耸听的说法是闭包会造成内存泄露. 局部变量本来应该在函数退出的时候被解除引用,但如果局部变量被封闭在闭包形成的环境中,那么这个局部变量就能一直生存下去.从这个意义上看,闭包的确会使一些数据无法被及时销毁.使用闭包的一部分原因是我们选择主动把一些变量封存在闭包中,因为可能在以后还需要使用这些变量,把这些变量放在闭包中和放在全局作用域,对内存方面的影响是一致的,这里并不能说成是内存泄露.如果在将来需要回收这些变量,我们可以手动把这些变量设为n