JavaScript内存泄漏

内存泄露是指一块被分配的内存既不能使用,又不能回收,直到浏览器进程结束。

在C++中,因为是手动管理内存,内存泄露是经常出现的事情。而现在流行的C#和Java等语言采用了自动垃圾回收方法管理内存,正常使用的情况下几乎不会发生内存泄露。

浏览器中也是采用自动垃圾回收方法管理内存,但由于浏览器垃圾回收方法有bug,会产生内存泄露。

导致内存泄漏的方式

不同的浏览器中存在各种内存泄露方式,目前发现的主要是这样几种:

1、循环引用

已经确认存在泄漏的浏览器:IE6.0 FF2.0

含有DOM对象的循环引用将导致大部分当前主流浏览器内存泄露 这里有两个简单的概念

引用:a.属性=b,a就引用了b

循环引用:简单来说假如a引用了b,b又引用了a,a和b就构成了循环引用。

a和b循环引用:

1 var a=new Object;
2 var b=new Object;
3 a.r=b;
4 b.r=a;

a循环引用自己:

1 var a=new Object;
2 a.r=a;

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

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

1 function bindEvent()
2 {
3     var obj=document.createElement("XXX");
4     obj.onclick=function(){
5         //Even if it's a empty function
6     }
7 }

这个bindEvent执行时100%会发生内存泄露,有人可能会问,哪里出现了循环引用? 关于closure和scope chain参与的循环引用比较复杂,此处暂不深入讨论。有一个简单的判断方式:函数将间接引用所有它能访问的对象。obj.onclick这个函数中 可以访问外部的变量obj 所以他引用了obj,而obj又引用了它,因此这个事件绑定将会造成内存泄露。

2、某些DOM操作

当原有的父DOM被移除时,子节点引用没有被移除则无法回收。

var select = document.querySelector;
var treeRef = select('#tree');

var leafRef = select('#leaf');   //在COM树中leafRef是treeRef的一个子结点

select('body').removeChild(treeRef);  //#tree不能被回收入,因为treeRef还在

解决方法:

treeRef = null;//tree还不能被回收,因为叶子结果leafRef还在
leafRef = null;//现在#tree可以被释放了

DOM插入顺序导致内存泄漏

当动态创建的2个不同范围的DOM对象附加到一起的时候,一个临时的对象会被创建。这个DOM对象改变范围到document时,那个临时对象就没用了,这个临时对象没有被回收将导致内存泄漏。如果我们将这两个DOM添加到原有的本就存在的DOM对象上就不会产生中间临时对象。

3、timer定时器泄漏

var val = 0;
for (var i = 0; i < 90000; i++) {
  var buggyObject = {
    callAgain: function() {
      var ref = this;
      val = setTimeout(function() {
        ref.callAgain();
      }, 90000);
  }
}
buggyObject.callAgain();

这个时候你无法回收buggyObject

//虽然你想回收但是timer还在
buggyObject = null;

解决办法,先停止timer然后再回收

//解决方法,先停止定时器
clearTimeout(val);
buggyObject = null;

4、自动类型装箱转换

下面的代码在ie系列中会导致内存泄漏

var s=”lalala”;
alert(s.length);

s本身是一个string而非object,它没有length属性,所以当访问length时,JS引擎会自动创建一个临时String对象封装s,而这个对象一定会泄露。这个bug匪夷所思,所幸解决起来相当容易,记得所有值类型做.运算之前先显式转换一下:

var s="lalala";
alert(new String(s).length);

5、意外的全局变量

Javascript 语言的设计目标之一是开发一种类似于 Java 但是对初学者十分友好的语言。体现 JavaScript 宽容性的一点表现在它处理未声明变量的方式上:一个未声明变量的引用会在全局对象中创建一个新的变量。在浏览器的环境下,全局对象就是 window,也就是说:

function foo(arg) {
    bar = "this is a hidden global variable";
}

实际上是:

function foo(arg) {
    window.bar = "this is an explicit global variable";
}
 

如果 bar 是一个应该指向 foo 函数作用域内变量的引用,但是你忘记使用 var 来声明这个变量,这时一个全局变量就会被创建出来。在这个例子中,一个简单的字符串泄露并不会造成很大的危害,但这无疑是错误的。

另外一种偶然创建全局变量的方式如下:

function foo() {
    this.variable = "potential accidental global";
}
// Foo called on its own, this points to the global object (window)
// rather than being undefined.
// 函数自身发生了调用,this 指向全局对象(window),(译者注:这时候会为全局对象 window 添加一个 variable 属性)而不是 undefined。
 
foo();

全局变量的注意事项

尽管我们在讨论那些隐蔽的全局变量,但是也有很多代码被明确的全局变量污染的情况。按照定义来讲,这些都是不会被回收的变量(除非设置 null 或者被重新赋值)。特别需要注意的是那些被用来临时存储和处理一些大量的信息的全局变量。如果你必须使用全局变量来存储很多的数据,请确保在使用过后将它设置为 null 或者将它重新赋值。常见的和全局变量相关的引发内存消耗增长的原因就是缓存。缓存存储着可复用的数据。为了让这种做法更高效,必须为缓存的容量规定一个上界。由于缓存不能被及时回收的缘故,缓存无限制地增长会导致很高的内存消耗。

原文地址:https://www.cnblogs.com/xuepei/p/6393211.html