什么操作可以造成内存泄露

由于疏忽或者错误造成程序未能释放已经不再使用的内存,不再用到的内存却没有及时释放,从而造成内存上的浪费。

原因

意外的全局变量

由于 js 对未声明变量的处理方式是在全局对象上创建该变量的引用。如果在浏览器中,全局对象就是 window 对象。变量在窗口关闭或重新刷新页面之前都不会被释放,如果未声明的变量缓存大量的数据,就会导致内存泄露。

  • 未声明变量
1
2
3
4
function fn() {
a = "global variable";
}
fn();
  • 使用 this 创建的变量(this 的指向是 window)。
1
2
3
4
function fn() {
this.a = "global variable";
}
fn();

解决方法

  • 避免创建全局变量
  • 使用严格模式,在 Javascript 文件头部或函数的顶部加上 use strict

闭包引起的内存泄露

闭包可以读取函数内部的变量,然后让这些变量始终保存在内存中。如果在使用结束后没有将局部变量清除,就可能导致内存泄露。

1
2
3
4
5
6
function fn() {
var a = "I'm a";
return function () {
console.log(a);
};
}

解决: 将事件处理函数定义在外部,解除闭包,或者在定义事件处理函数的外部函数中。

比如:在循环中的函数表达式,能复用最好放到循环外面。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// bad
for (var k = 0; k < 10; k++) {
var t = function (a) {
// 创建了10次 函数对象。
console.log(a);
};
t(k);
}

// good
function t(a) {
console.log(a);
}
for (var k = 0; k < 10; k++) {
t(k);
}
t = null;

没有清理的 DOM 元素引用

虽然别的地方删除了,但是对象中还存在对 dom 的引用。

1
2
3
4
5
6
7
8
9
10
11
12
13
// 在对象中引用DOM
var elements = {
btn: document.getElementById("btn"),
};
function doSomeThing() {
elements.btn.click();
}

function removeBtn() {
// 将body中的btn移除, 也就是移除 DOM树中的btn
document.body.removeChild(document.getElementById("button"));
// 但是此时全局变量elements还是保留了对btn的引用, btn还是存在于内存中,不能被GC回收
}

解决: 手动删除,elements.btn = null

被遗忘的定时器或者回调

定时器中有 dom 的引用,即使 dom 删除了,但是定时器还在,所以内存中还是有这个 dom

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 定时器
var serverData = loadData();
setInterval(function () {
var renderer = document.getElementById("renderer");
if (renderer) {
renderer.innerHTML = JSON.stringify(serverData);
}
}, 5000);

// 观察者模式
var btn = document.getElementById("btn");
function onClick(element) {
element.innerHTMl = "I'm innerHTML";
}
btn.addEventListener("click", onClick);

解决:

  • 手动删除定时器和 dom。
  • removeEventListener 移除事件监听