常见的内存泄露

225 阅读2分钟

什么是内存泄露

内存泄露不是指内存凭空消失了,而是内存满了溢出了。意指不再使用的内存没有得到释放。

如果页面中存在内存泄露的程序,每刷新一遍都有新的内存被占据,那就会越来越卡顿,最终可能会导致页面崩溃。

内存生命周期

内存的生命周期一般会经历:内存分配内存使用内存释放。如下图所示:

image.png

当我们声明一个变量时,就给这个变量分配内存;然后可能会对内存进行读、写或运算等操作,就是内存使用过程;读写结束后会将不再用到的内存空间进行释放,再进入下一内存周期,循环往复。

标记-清除垃圾回收算法

内存释放的过程,使用的标记-清除垃圾回收算法。是从根节点开始递归标记出不再使用的变量,在javascript中根节点是window,垃圾回收器会定期对全局变量进行标记,然后清除。

4种内存泄露

未声明/意外的全局变量

思考如下代码:

var a = 1
function foo () {
  b = 2 // 非严格模式下,会被申明为全局变量
}
console.log(b) // 2

全局变量不会轻易被回收,只有等到页面关闭后。如果页面上的全局变量太多,势必会影响内存的占用。极端情况下可能会造成内存泄露。

定时器

我们常用的定时器:setTimeoutsetInterval。都需要手动来清除。

var intervalID = setInterval(myCallback, 500, 'Parameter 1', 'Parameter 2')
function myCallback(a, b) {
  // Your code here
  // Parameters are purely optional.
  console.log(a)
  console.log(b)
}
// 当不需要使用时,清除定时器
clearInterval(intervalID)

这里setIntervalsetTimeout是共享同一个ID池,也就是说setInterval生成的intervalID,可以通过clearTimeout(intervalID)来清除。

监听器

如常用的addEventListener()

// html
<div id="e1">click me</div>

const el = document.getElementById('e1')
el.addEventListener('click', modifyText, false)

// 在不需要监听下,需要手动remove
el.removeEventListener('click', modifyText, false)

需要注意的是,第三个参数useCapture也需要对应,否则无法清除。

闭包

闭包本质上是一个外部作用域能够访问到某个函数内部作用域变量。这样如果外部变量一直存在,那函数则一直不会被回收。

思考如下代码:

function f1 () {
  var a = 1
  function f2 () {
    return ++a
  }
  return f2
}
var f = f1()
f() // 1
f() // 2

从输出可以看出来,变量a是一直存在于内存的,不会被垃圾回收。

我们可以修改:var f = null。让f1函数不会再被引用而被垃圾回收掉。