垃圾回收算法
分配内存
JavaScript 在定义变量时就完成了内存分配
// 给数值变量分配内存
var num = 123
// 给字符串分配内存
var str = 'a string'
// 给对象及其包含的值分配内存
var obj = {
a: 1,
b: null
}
// 给数组及其包含的值分配内存
var arr = [1, null, 'string']
// 给函数(可调用的对象)分配内存
function func (a) {
return a + 2
}
// 函数表达式也能分配一个对象
someElement.addEventListener('click', function(){
someElement.style.backgroundColor = 'blue'
}, false)
通过函数调用分配内存
有些函数调用结果是分配对象内存
// 分配一个 Date 对象
var d = new Date()
// 分配一个 dom 元素
var e = document.createElement('div')
有些方法分配新变量或者新对象
var s = 'string'
// s2 是一个新的字符串
var s2 = s.substr(0, 3)
// 因为字符串是不变量
// JavaScript 可能决定不分配内存
// 只是存储了 [0 - 3] 的范围
var a = ['first first', 'second second']
var a2 = ['one one', 'second second']
var a3 = a.concat(a2)
// 新数组有四个元素,是 a 与 a2 合并的结果
使用值
使用值的过程实际上是对分配内存进行读取与写入的操作。读取与写入可能是写入一个变量或者一个对象的属性值,甚至传递函数的参数
当内存不再需要使用时释放
它往往要求开发人员来确定在程序中哪一块内存不再需要并且释放它
高级语言解释器嵌入了“垃圾回收器”,它的主要工作是跟踪内存的分配和使用,以便当分配的内存不再使用时,自动释放它。这只能是一个近似的过程,因为要知道是否仍然需要某块内存是无法判定的(无法通过某种算法解决)。
垃圾回收
引用
垃圾回收算法主要依赖于引用的概念。在内存管理的环境中,一个对象如果有访问另一个对象的权限(隐式或者显式),叫做一个对象引用另一个对象。例如,一个 JS 对象具有对它原型的引用(隐式引用)和对它属性的引用(显式引用)
引用计数垃圾收集
最初级的垃圾收集算法
此算法把 '对象是否不再需要' 简化定义为 '对象有没有其他对象引用到它' 。如果没有引用指向该对象(零引用),对象将被垃圾回收机制回收
示例:
// 创建对象
var obj = {
a: {
b: 2
}
}
// 两个对象被创建,一个作为另一个的属性被引用,另一个被分配给变量 obj
// 很显然,没有一个可以被垃圾收集
// obj2 变量是第二个对 '这个对象' 的引用
var obj2 = obj
// 现在,'这个对象' 的原始引用 obj 被 obj2 替换了
obj = 1
// 引用 '这个对象' 的 a 属性
var obja = obj2.a
// 现在,'这个对象' 有两个引用了,一个是 obj2 ,一个是 obja
o2 = 'yo'
// 最初的对象现在已经是零引用了
// 它可以被垃圾回收了
// 然而它的属性 a 的对象还在被 obja 引用,所以还不能回收
// a 属性的那个对象现在也是零引用了
// 它可以被垃圾回收了
obja = null
限制:循环引用
该算法有个限制:无法处理循环引用。
示例:
function func(){
var obj = {}
var obj2 = {}
// obj 引用 obj2
obj.a = obj2
// obj2 引用 obj
obj2.a = obj
return '123'
}
func()
// 两个对象被创建,并相互引用,形成一个循环,被调用后即使已经离开函数作用域,已经没有用了,但是它们至少还存在一个引用,所以它们不会被回收
实际例子:
IE 6 , 7 使用引用计数方式对 DOM 对象进行垃圾回收。该方式常常造成对象被循环引用时内存发生泄露
var div
window.onload = function(){
div = document.getElementById('myDivElement')
div.circularReference = div
div.lotsOfData = new Array(10000).join('*')
}
在上面的例子里,myDivElement
这个 DOM 元素里的 circularReference
属性引用了 myDivElement
,造成了循环引用。如果该属性没有显式移除或者设为 null,引用计数式垃圾收集器将总是且至少有一个引用,并将一直保持在内存里的 DOM 元素,即使其从 DOM 树种删除了。如果这个 DOM 元素拥有大量的数据(如上的 lotsOfData 属性),则这个数据占用的内存将永远不会释放
标记-清除算法
这个算法把 '对象是否不再需要' 简化定义为 '对象是否可以获得'
这个算法假定设置一个叫做根(root) 的对象(在 js 里,根是全局对象)。垃圾回收器将定期从根开始,找所有从根开始引用的对象,然后找这些对象引用的对象......从根开始,垃圾回收器将找到所有可以获得的对象和收集所有不能获得的对象
这个算法比 引用计数清除法 要好,因为 '有零引用的对象' 总是不可获得的,但是相反却不一定
从 2012 年起,所有现代浏览器都使用了 标记-清除垃圾回收算法
循环引用不再是问题了
在上面的示例种,函数调用返回之后,两个对象从全局对象出发无法获取。因此,它们将会被垃圾回收器回收。
第二个示例同样,一旦 div 和其他事件处理无法从根获取到,它们将会被垃圾回收器回收
限制:那些无法从根对象查询到的对象都将被清除
尽管这是一个限制,但实践种很少会碰到类似的情况,所以开发者不太会去关心垃圾回收机制
参考 内存管理 MDN