JS中4种常见的内存泄漏

时间:2022-03-22 20:56:16

一、什么是内存泄漏

本质上讲,内存泄漏是当一块内存不再被应用程序使用的时候,由于某种原因,这块内存没有返还给操作系统或空闲内存池的现象。

二、几种常见的内存泄漏

1、意外的全局变量

一个未声明变量的引用会在全局对象中创建一个新的变量。在浏览器的环境下,全局对象就是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();

上面代码中,foo函数再全局作用域中被调用,因此this指向window

全局变量的注意事项:

如果需要全局变量来存储很多数据,必须确保在使用过后将它设置为null或重新为他赋值。

常见的和全局变量相关的引发内存消耗增长的原因是缓存。缓存存储着可复用的数据。

为了让这种做法更高效,必须为缓存的容量规定一个上界。由于缓存不能被及时回收的缘故,缓存无限制地增长会导致很高的内存消耗。

 

2、闭包引起的内存泄漏

闭包可以使变量常驻内存,但如果使用不当就会在成内存泄漏

var theThing = null;
var replaceThing = function () {
  var originalThing = theThing;
  var unused = function () {
    if (originalThing)
      console.log("hi");
  };
  theThing = {
    longStr: new Array(1000000).join('*'),
    someMethod: function () {
      console.log(someMessage);
    }
  };
};
setInterval(replaceThing, 1000);

上面代码中,每次调用 replaceThing 时,theThing 都会得到新的包含一个大数组和新的闭包(someMethod)的对象。

同时,没有用到的那个变量持有一个引用了 originalThingreplaceThing 调用之前的 theThing)闭包。

关键的问题是每当在同一个父作用域下创建闭包作用域的时候,这个作用域是被共享的。在这种情况下,someMethod 的闭包作用域和 unused 的作用域是共享的。

unused 持有一个 originalThing 的引用。尽管 unused 从来没有被使用过,someMethod 可以在 theThing 之外被访问。

而且 someMethod 和 unused 共享了闭包作用域,即便 unused 从来都没有被使用过,它对 originalThing 的引用还是强制它保持活跃状态(阻止它被回收)。

当这段代码重复运行时,将可以观察到内存消耗稳定地上涨,并且不会因为 GC 的存在而下降。

本质上来讲,创建了一个闭包链表(根节点是 theThing 形式的变量),而且每个闭包作用域都持有一个对大数组的间接引用,这导致了一个巨大的内存泄露。

3、DOM之外的引用

var elements={  
    button: document.getElementById("button"),  
    image: document.getElementById("image"),  
    text: document.getElementById("text")  
};  
function doStuff(){  
    image.src="http://some.url/image";  
    button.click():  
    console.log(text.innerHTML)  
}  
function removeButton(){  
    document.body.removeChild(document.getElementById('button'))  
}  

 

2、被遗漏的定时器和回调函数

var someResouce=getData();  
setInterval(function(){  
    var node=document.getElementById('Node');  
    if(node){  
        node.innerHTML=JSON.stringify(someResouce)  
    }  
},1000) 

上面代码中, 如果 id 为 Node 的元素从 DOM 中移除, 该定时器仍会存在, 同时, 因为回调函数中包含对 someResource 的引用, 定时器外面的 someResource 也不会被释放。

三、怎样避免内存泄漏

1)减少不必要的全局变量,或者生命周期较长的对象,及时对无用的数据进行垃圾回收;

2)注意程序逻辑,避免“死循环”之类的 ;

3)避免创建过多的对象  原则:不用了的东西要及时归还。