js内存管理机制

JavaScript是在创建变量(对象,字符串等)时自动进行了分配内存,并且在不使用它们时“自动”释放。 释放的过程称为垃圾回收。

js存储

JavaScript中基础类型是保存在栈(stack)中的,会自动进行回收;而复合类型是保存在堆(dump)中的,通过 GC 操作进行空间释放。

值的初始化

JavaScript 在定义变量时就完成了内存分配。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
var n = 123; // 给数值变量分配内存 存在栈中
var s = "azerty"; // 给字符串分配内存 存在栈中

var o = {
a: 1,
b: null
}; // 给对象及其包含的值分配内存 指针存在栈中 值存在堆中

// 给数组及其包含的值分配内存(就像对象一样)
var a = [1, null, "abra"];

function f(a) {
return a + 2;
} // 给函数(可调用的对象)分配内存

// 函数表达式也能分配一个对象
someElement.addEventListener('click', function(){
someElement.style.backgroundColor = 'blue';
}, false);

通过函数调用分配内存

有些函数调用结果是分配对象内存:

1
2
var d = new Date(); // 分配一个 Date 对象
var e = document.createElement('div'); // 分配一个 DOM 元素

有些方法分配新变量或者新对象:

1
2
3
4
5
6
7
8
9
10
var s = "azerty";
var s2 = s.substr(0, 3); // s2 是一个新的字符串
// 因为字符串是不变量,
// JavaScript 可能决定不分配内存,
// 只是存储了 [0-3] 的范围。

var a = ["ouais ouais", "nan nan"];
var a2 = ["generation", "nan nan"];
var a3 = a.concat(a2);
// 新数组有四个元素,是 a 连接 a2 的结果

使用值

使用值的过程实际上是对分配内存进行读取与写入的操作。读取与写入可能是写入一个变量或者一个对象的属性值,甚至传递函数的参数。

当内存不再需要使用时释放

高级语言解释器嵌入了“垃圾回收器”,它的主要工作是跟踪内存的分配和使用,以便当分配的内存不再使用时,自动释放它。

垃圾回收

垃圾回收算法主要依赖于引用的概念。在内存管理的环境中,一个对象如果有访问另一个对象的权限(隐式或者显式),叫做一个对象引用另一个对象。

引用计数器算法

引用计数器是最初级的垃圾收集算法。此算法把“对象是否不再需要”简化定义为“对象有没有其他对象引用到它”。如果没有引用指向该对象(零引用),对象将被垃圾回收机制回收。该算法有个限制:无法处理循环引用的事例。

标记-清除算法

这个算法把“对象是否不再需要”简化定义为“对象是否可以获得”。这个算法假定设置一个叫做根(root)的对象(在 Javascript 里,根是全局对象)。垃圾回收器将定期从根开始,找所有从根开始引用的对象,然后找这些对象引用的对象……从根开始,垃圾回收器将找到所有可以获得的对象和收集所有不能获得的对象。

从2012年起,所有现代浏览器都使用了标记-清除垃圾回收算法。所有对JavaScript垃圾回收算法的改进都是基于标记-清除算法的改进,并没有改进标记-清除算法本身和它对“对象是否不再需要”的简化定义。

js内存溢出的几种情况

  1. 给DOM对象添加的属性是一个对象的引用
1
2
var MyObject = {};
document.getElementById('myDiv').myProp = MyObject;

解决方法:
在window.onunload事件中写上:

1
document.getElementById('myDiv').myProp = null;
  1. DOM对象与JS对象相互引用
1
2
3
4
5
function Encapsulator(element) {
this.elementReference = element;
element.myProp = this;
}
new Encapsulator(document.getElementById('myDiv'));

解决方法:
在onunload事件中写上:

1
document.getElementById('myDiv').myProp = null;
  1. 给DOM对象用addEventListener绑定事件
1
2
function doClick() {}
element.addEventListener("onclick", doClick);

解决方法:
在onunload事件中写上:

1
element.removeEventListener('onclick', doClick);
  1. 从外到内执行 appendChild 这时即使调用 removeChild 也无法释放
1
2
3
4
var parentDiv =  document.createElement("div");
var childDiv = document.createElement("div");
document.body.appendChild(parentDiv);
parentDiv.appendChild(childDiv);

解决方法:
从内到外执行appendChild:

1
2
3
4
var parentDiv =  document.createElement("div");
var childDiv = document.createElement("div");
parentDiv.appendChild(childDiv);
document.body.appendChild(parentDiv);
  1. 反复重写同一个属性会造成内存大量占用
1
2
3
for(i = 0; i < 5000; i++) {
hostElement.text = "asdfasdfasdf";
}