JS内存泄露

457 阅读4分钟

这是我参与8月更文挑战的第25天,活动详情查看:8月更文挑战

什么是内存泄漏

程序的运行需要内存。只要程序提出要求,操作系统或者运行时(runtime)就必须供给内存。 对于持续运行的服务进程(daemon),必须及时释放不再用到的内存。否则,内存占用越来越高,轻则影响系统性能,重则导致进程崩溃。不再用到的内存,没有及时释放,就叫做内存泄漏(memory leak)。

意外的全局变量

在 JavaScript 非严格模式中,未定义的变量会自动绑定在全局对象上(window/global)

var bar = '';
 function foo(){
 bar='something'
 }
 
 foo()

foo 执行的时候,由于内部变量没有定义,所以相当于 window.bar = 'something',函数执行完毕,本应该被销毁的变量 bar 却永久的保留在内存中了。

由this创建的全局变量

 function foo(){
    this.test = '345'
 }
 
 foo()

解决办法,使用严格模式

虽然全局变量上绑定的变量无法被垃圾回收,但是有时需要使用全局变量去存储临时信息,这个时候要格外小心,并在变量使用完毕后设置为 null,以回收内存。

全局变量注意事项

尽管我们讨论了一些意外的全局变量,但是仍有一些明确的全局变量产生的垃圾。它们被定义为不可回收(除非定义为空或重新分配)。尤其当全局变量用于临时存储和处理大量信息时,需要多加小心。如果必须使用全局变量存储大量数据时,确保用完以后把它设置为 null 或者重新定义。与全局变量相关的增加内存消耗的一个主因是缓存。缓存数据是为了重用,缓存必须有一个大小上限才有用。高内存消耗导致缓存突破上限,因为缓存内容无法被回收。

#不规范地使用闭包 闭包是 JavaScript 开发的一个关键方面:匿名函数可以访问父级作用域的变量。

代码示例: 相互循环引用.这是经常容易犯的错误,并且有时也不容易发现.

function  foo() {
  var a =  {};
  function  bar() {
  console.log(a);
};
  a.fn = bar;
  return bar;
};

bar和a形成了相互循环引用.可能有人说bar里不使用console.log(a)不就没有引用了吗就不会造成内存泄露了.NONONO,bar作为一个闭包,即使它内部什么都没有,foo中的所有变量都还是隐式地被 bar所引用。即使bar内什么都没有还是造成了循环引用,那真正的解决办法就是,不要将a.fn = bar.

注意事项

避免策略:

  1. 减少不必要的全局变量,或者生命周期较长的对象,及时对无用的数据进行垃圾回收(即赋值为null);
  2. 注意程序逻辑,避免“死循环”之类的 ;
  3. 避免创建过多的对象 原则:不用了的东西要记得及时归还。
  4. 减少层级过多的引用

被遗忘的定时器和回调函数

在很多库中, 如果使用了观察者模式, 都会提供回调方法, 来调用一些回调函数。 要记得回收这些回调函数。举一个 setInterval的例子:

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

如果后续 renderer 元素被移除,整个定时器实际上没有任何作用。 但如果你没有回收定时器,整个定时器依然有效, 不但定时器无法被内存回收, 定时器函数中的依赖也无法回收。在这个案例中的 serverData 也无法被回收。

DOM 引用

很多时候,为了方便存取,经常会将 DOM 结点暂时存储到数据结构中.但是在不需要该DOM节点时,忘记解除对它的引用,则会造成内存泄露.

var element =  {
  shotCat:  document.getElementById('shotCat')
};
document.body.removeChild(document.getElementById('shotCat'));
// 如果element没有被回收,这里移除了 shotCat 节点也是没用的

上述案例中,即使我们对于 shotCat 进行了移除,但是仍然有对 shotCat 元素的引用,依然无法对齐进行内存回收。

另外需要注意的一个点是,对于一个 Dom 树的叶子节点的引用。

举个例子: 如果我们引用了一个表格中的td元素,一旦在 Dom 中删除了整个表格,我们直观的觉得内存回收应该回收除了被引用的 td 外的其他元素。

但是事实上,这个 td 元素是整个表格的一个子元素,并保留对于其父元素的引用。

这就会导致对于整个表格,都无法进行内存回收。所以我们要小心处理对于 Dom 元素的引用。

如何避免内存泄漏

记住一个原则:不用的东西,及时归还。

  1. 减少不必要的全局变量,使用严格模式避免意外创建全局变量。
  2. 在你使用完数据后,及时解除引用(闭包中的变量,dom引用,定时器清除)。
  3. 组织好你的逻辑,避免死循环等造成浏览器卡顿,崩溃的问题。