Javascript中出现内存泄漏的主要原因是什么

1)循环引用

一个很简单的例子:一个DOM对象被一个Javascript对象引用,与此同时又引用同一个或其它的Javascript对象,这个DOM对象可能会引发内存泄漏。这个DOM对象的引用将不会在脚本停止的时候被垃圾回收器回收。要想破坏循环引用,引用DOM元素的对象或DOM对象的引用需要被赋值为null。由于引用计数导致的错误.

 

2)Javascript闭包

因为Javascript范围的限制,许多实现依赖Javascript不包,请查看我的前面的文章JavaScript Scope and Closure如果你想了解更多闭包方面的问题。闭包可以导致内存泄漏是因为内部方法保持一个对外部方法变量的引用,所以尽管方法返回了内部方法还可以继续访问在外部方法中定义的私有变量。对Javascript程序员来说最好的做法是在页面重载前断开所有的事件处理器。

尤其是当碰到Closure,当我们往Native对象(例如Dom对象、ActiveX Object)上绑定事件响应代码时,一个不小心,我们就会制造出Closure Memory Leak。其关键原因,其实和前者是一样的,也是一个跨javascript objectnative object的循环引用。只是代码更为隐蔽,这个隐蔽性,是由于javascript的语言特性造成的。但在使用类似内嵌函数的时候,内嵌的函数有拥有一个reference指向外部函数的scope,包括外部函数的参数,因此也就很容易造成一个很隐蔽的循环引用.

3)DOM插入顺序

2个不同范围的 DOM对象连添加到一起的时候一个临时的对象会被创建。这个DOM对象改变范围到document时,那个临时对象就没用了。也就是说, DOM 对象应该按照从当前页面存在的最上面的 DOM元素开始往下直到剩下的 DOM元素的顺序添加,这样它们就总是有同样的范围,不会产生临时对象。

 

循环引用很常见且大部分情况下是无害的,但当参与循环引用的对象中有 DOM 对象或者 ActiveX 对象时,循环引用将导致内存泄露。我们把例子中的任何一个 new Object 替换成 document.getElementById 或者 document.createElement 就会发生内存泄露了。

尽管这看起来非常容易理解,但是因为有closure的参与而使事情变得复杂,有些closure导致的循环引用很难被察觉。下面是一个非常常见的动态绑定事件:

function bindEvent()
{
    var obj=document.createElement("XXX");
    obj.οnclick=function(){
    //Even if it's a empty function
    }
}

这个bindEvent执行时100%会发生内存泄露,Someone可能会问,哪里出现了循环引用?关于closurescope chain参与的循环引用比较复杂,此处暂不深入讨论。有一个简单的判断方式:函数将间接引用所有它能访问的对象。obj.onclick这个函数中可以访问外部的变量obj所以他引用了obj,obj又引用了它,因此这个事件绑定将会造成内存泄露。在IBM的文章中介绍了2种方式解决类似的问题一个是obj=null,另一个是把onclick的函数写在bindEvent外,重复人家的我就不说了。简单贴下代码:

function bindEvent()
{
    var obj=document.createElement("XXX");
    obj.οnclick=onclickHandler;
}

function onclickHandler(){
   //do something
}
	
function bindEvent()
{
    var obj=document.createElement("XXX");
    obj.οnclick=function(){
    //Even if it's a empty function
   }
   obj=null;
}

这两个方法都打断了循环引用,可以解决问题,但是似乎对代码表达能力造成了一定破坏,假设有这么一个问题:

function bindEvent()
{
    var obj=document.createElement("XXX");
    var var0="OOXX";//Here is a variable
    obj.οnclick=function(){
        alert(var0);//I want to visit var2 here!
    }
    return obj;//bindEvent must return obj!
}

好了,这下两种办法都不行了,假如我把函数写外面去,var0肯定访问不了,假如我把obj弄成null,还怎么return它呢?这并不是空想的需要,这实际上是一个用JS定制DOM控件的简单抽象:创建DOM元素、设置私有属性、绑定事件。所以,我们必须update一下两个方法。首先,方法1,为了让函数能访问某些变量,我们可以通过一个Builder函数来订制onclick的外部闭包:

function bindEvent()
{
    var obj=document.createElement("XXX");
    var var0="OOXX";//Here is a variable
    obj.οnclick= onclickBuilder(var0);//想访问谁就把谁传进去!!
    return obj;//bindEvent must return obj!
}

function onclickBuilder(var0)//这里跟上面对应上就行了最好参数名字也对应上
{
    return function(){
          alert(var0);
    }
}

第二个办法,这个来自51jschpn同学,让obj=nullreturn之后执行!

原因是LeakMemory()会先建立起parentDiv和childDiv之间的连接,这时候,为了让 childDiv能够获知parentDiv的信息,因此IE需要先建立一个临时的scope对象。

而后parentDiv建立了和 hostElement对象的联系,parentDiv和childDiv直接使用页面document的scope。可惜的是,IE不会释放刚才那个临时的scope对象的内存空间,

直到我们跳转页面,这块空间才能被释放。而CleanMemory函数不同,他先把parentDiv和 hostElement建立联系,而后再把childDiv和parentDiv建立联系,

这个过程不需要单独建立临时的scope,只要直接使用页面 document的scope就可以了,所以也就不会造成内存泄露了.

怎样处理这样的memory leak呢?

首先我们定义一个看不见的元素当作垃圾箱,所有新创建的元素都扔进垃圾箱里,这样保证了所有DOM元素都在DOM树上,IE7就可以正确回收了,

另一方面也能避免所谓的”appendChild顺序不对导致内存泄露”。

其它一些琐碎的注意点

变量定义一定要用var,否则隐式声明出来的变量都是全局变量,不是局部变量
全局变量没用时记得要置null;
注意正确使用delete,删除没用的一些函数属性;
注意正确使用try...cache,确保去处无效引用的代码能被正确执行;
open出来的窗口即使close了,它的window对象还是存在的,要记得删除引用;
frame和iframe的情况和窗口的情况类似。



REF

http://www.blogjava.net/tim-wu/archive/2006/05/29/48729.html

http://blog.jobbole.com/18834/

http://amix.dk/blog/post/19564

http://www.ibm.com/developerworks/web/library/wa-memleak/

http://javascript.info/tutorial/memory-leaks


function bindEvent()
{
  try{
          var obj=document.createElement("XXX");
          var var0="OOXX";//Here is a variable
          obj.οnclick=function(){
                  alert(var0);//I want to visit var2 here!
          }
         return obj;//bindEvent must return obj!

   } finally {
         obj=null;
   }
}
function LeakMemory() // 这个函数会引发Cross-Page Leaks
{
var hostElement = document.getElementById( " hostElement " );

// Do it a lot, look at Task Manager for memory response 
for (i = 0 ; i < 5000 ; i ++ )
{
  var parentDiv = 
    document.createElement( "<div onClick='foo()'> " );
  var childDiv = 
    document.createElement( "<div onClick='foo()'> " );

  // This will leak a temporary object 
  parentDiv.appendChild(childDiv);
  hostElement.appendChild(parentDiv);
  hostElement.removeChild(parentDiv);
  parentDiv.removeChild(childDiv);
  parentDiv = null ;
  childDiv = null ;
}
hostElement = null ;
}

function CleanMemory() // 而这个函数不会引发Cross-Page Leaks
{
var hostElement = document.getElementById( " hostElement " );

// Do it a lot, look at Task Manager for memory response 
for (i = 0 ; i < 5000 ; i ++ )
{
    var parentDiv =document.createElement( " <div onClick='foo()'> " );
    var childDiv =document.createElement( " <div onClick='foo()'> " );

    // Changing the order is important,this won't leak 
    hostElement.appendChild(parentDiv);
    parentDiv.appendChild(childDiv);
    hostElement.removeChild(parentDiv);
    parentDiv.removeChild(childDiv);
    parentDiv = null;
    childDiv = null;
}
    hostElement = null ;
}

function MemoryFix(){ 
      var garbageBox=document.createElement("div"); 
      garbageBox.style.display="none"; 
      document.body.appendChild(garbageBox); 
      var createElement=document.createElement; 
      document.createElement=function(){ 
            var obj=Function.prototype.apply.apply(createElement,[document,arguments]); 
            garbageBox.appendChild(obj); return obj; 
      }
 }



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值