存在意义
JS的垃圾回收机制是为了以防内存泄漏,内存泄漏的含义就是当已经不需要某块内存时这块内存还存在着,垃圾回收机制就是间歇的不定期的寻找到不再使用的变量,并释放掉它们所指向的内存。
在JS中,JS的执行环境会负责管理代码执行过程中使用的内存。
可达性
JavaScript 中内存管理的主要概念是可达性。
- 有一组基本的固有可达值,由于显而易见的原因无法删除。例如:
- 本地函数的局部变量和参数
- 当前嵌套调用链上的其他函数的变量和参数
- 全局变量
- 还有一些其他的,内部的
- 这些值称为根。
- 如果引用或引用链可以从根访问任何其他值,则认为该值是可访问的。
例如,如果局部变量中有对象,并且该对象具有引用另一个对象的属性,则该对象被视为可达性, 它引用的那些也是可以访问的,详细的例子如下。
JavaScript 引擎中有一个后台进程称为垃圾回收器,它监视所有对象,并删除那些不可访问的对象。
相互关联的对象
function marry (man, woman) {
woman.husban = man;
man.wife = woman;
return {
father: man,
mother: woman
}
}
let family = marry({
name: “John”
}, {
name: “Ann”
})
内部算法-引用计数(reference counting)
在内存管理环境中,对象 A 如果有访问对象 B 的权限,叫做对象 A 引用对象 B。引用计数的策略是将“对象是否不再需要”简化成“对象有没有其他对象引用到它”,如果没有对象引用这个对象,那么这个对象将会被回收。
let obj1 = { a: 1 }; // 一个对象(称之为 A)被创建,赋值给 obj1,A 的引用个数为 1
let obj2 = obj1; // A 的引用个数变为 2
obj1 = 0; // A 的引用个数变为 1
obj2 = 0; // A 的引用个数变为 0,此时对象 A 就可以被垃圾回收了
但是引用计数有个最大的问题: 循环引用。
function func() {
let obj1 = {};
let obj2 = {};
obj1.a = obj2; // obj1 引用 obj2
obj2.a = obj1; // obj2 引用 obj1
}
创建了两个对象,并相互引用,形成了一个循环,被调用之后离开了函数作用域,
所以已经没有用了,可以被回收了,但是之间相互引用,所以不会被回收。
要解决循环引用的问题,最好是在不使用它们的时候手工将它们设为空。
上面的例子可以这么做:
obj1 = null;
obj2 = null;
内部算法-标记-清除算法
这个算法把“对象是否是垃圾”简化定义为“对象是否可以获得”,如果找不到就是垃圾,将被垃圾回收机制回收。
分为标记和清除两个阶段
标记阶段,垃圾回收器会从根对象开始遍历。每一个可以从根对象访问到的对象都会被添加一个标识,于是这个对象就被标识为可到达对象。
清除阶段,垃圾回收器会对堆内存从头到尾进行线性遍历,如果发现有对象没有被标识为可到达对象,那么就将此对象占用的内存回收,并且将原来标记为可到达对象的标识清除,以便进行下一次垃圾回收操作。
清除所有标记,执行下一轮操作(因为内存会动态的发生改变)
即使出现两个循环引用,也没有关系,从根开始找不到他们,没有标记上,后面也会被清除掉
内存泄漏
意外的全局变量
function fn() {
a = new Array(10000000)
console.log(a)
}
fn()
当全局变量用于临时存储和处理大量信息时,需要多加小心。如果必须使用全局变量存储大量数据时,确保用完以后把它设置为 null 或者重新定义。
没有及时清理的计时器或回调函数
没有及时清理的计时器或回调函数
var intervalId = setInterval(function () { //启动循环定时器后不清理
console.log('----')
}, 1000)
// clearInterval(intervalId)
闭包
闭包 是指有权访问另一个函数作用域中的变量的函数,创建闭包的最常见的方式就是在一个函数内创建另一个函数,通过另一个函数访问这个函数的局部变量
function assignHandler(){
var element = document.getElementById("someElement");
var id = element.id;
element.onclick = function(){
alert(id);
};
element = null;
}
由于闭包会使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包,否则会造成网页的性能问题,在IE中可能导致内存泄露。解决方法是,在退出函数之前,将不使用的局部变量全部删除。
这是因为IE浏览器早期是使用引用计数法,如果两个对象之间形成了循环引用,那么这两个对象都无法被回收,但循环引用造成的内存泄露在本质上不是闭包造成的。
减少回收的优化
1、对象object优化
为了最大限度的实现对象的重用,应该像避使用new语句一样避免使用{}来新建对象。 有一种方式能够保证对象(确保对象prototype上没有属性)的重复利用,那就是遍历此对象的所有属性,并逐个删除,最终将对象清理为一个空对象。
cr.wipe = function (obj) {
for (var p in obj) {
if (obj.hasOwnProperty(p))
delete obj[p];
}
};
可以使用cr.wipe(obj)方法清理对象,再为obj添加新的属性,就可以达到重复利用对象的目的。虽然通过清空一个对象来获取“新对象”的做法,比简单的通过{}来创建对象要耗时一些,但是在实时性要求很高的代码中,这一点短暂的时间消耗,将会有效的减少垃圾堆积,并且最终避免垃圾回收暂停
2、 数组array优化
清空数组的捷径: arr = [];这种方式又创建了一个新的空对象,并且将原来的数组对象变成了一小片内存垃圾!
arr.length = 0也能达到清空数组的目的,并且同时能实现数组重用,减少内存垃圾的产生。
3、方法function优化
function func() {
return function() {}; //每次调用函数时都会创建一个新的对象:
}
方法一般都是在初始化的时候创建,并且此后很少在运行时进行动态内存分配。在setTimeout中使用以上形式会造成问题。