什么是内存泄露
内存泄露不是指内存凭空消失了,而是内存满了溢出了。意指不再使用的内存没有得到释放。
如果页面中存在内存泄露的程序,每刷新一遍都有新的内存被占据,那就会越来越卡顿,最终可能会导致页面崩溃。
内存生命周期
内存的生命周期一般会经历:内存分配、内存使用,内存释放。如下图所示:
当我们声明一个变量时,就给这个变量分配内存;然后可能会对内存进行读、写或运算等操作,就是内存使用过程;读写结束后会将不再用到的内存空间进行释放,再进入下一内存周期,循环往复。
标记-清除垃圾回收算法
内存释放的过程,使用的标记-清除垃圾回收算法。是从根节点开始递归标记出不再使用的变量,在javascript中根节点是window,垃圾回收器会定期对全局变量进行标记,然后清除。
4种内存泄露
未声明/意外的全局变量
思考如下代码:
var a = 1
function foo () {
b = 2 // 非严格模式下,会被申明为全局变量
}
console.log(b) // 2
全局变量不会轻易被回收,只有等到页面关闭后。如果页面上的全局变量太多,势必会影响内存的占用。极端情况下可能会造成内存泄露。
定时器
我们常用的定时器:setTimeout和setInterval。都需要手动来清除。
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)
这里setInterval和setTimeout是共享同一个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函数不会再被引用而被垃圾回收掉。