变量的生命周期
Javascript 变量的生命周期要分开来看,对于全局变量,他的生命周期会持续到页面关闭(这就涉及到了后面要总结的内存泄漏的一种方式).而对于局部变量,在所在的函数的代码执行之后,局部变量的生命周期结束,他所占用的内存会通过垃圾回收机制释放(即垃圾回收)。
垃圾回收机制
垃圾回收通常有两种方式来实现:
引用计数( IE )
这种算法在MDN文档中被称为最”天真”的垃圾回收算法;核心原理是: 判断一个对象是否要被回收就是要看是否还有引用指向它,如果是”零引用”,那么就回收.说这种算法天真,是因为它存在着较为严重的缺陷—-循环引用。
标记清除
这个算法把“对象是否不再需要”简化定义为“对象是否可以获得”。
这个算法假定有一个根(root)的对象;在 Javascript 里,根是全局对象,对应于浏览器环境的 window,node 环境的 global.垃圾回收器将定期从根开始,找所有从根开始引用的对象,然后找这些对象引用的对象……从根开始,垃圾回收器将找到所有可以获得的对象和收集所有不能获得的对象.
这个算法相对于引用计数的优势在于,“有零引用的对象”总是不可获得的,但是相反却不一定,参考“循环引用”。
从2012年起,所有现代浏览器都使用了标记-清除垃圾回收算法,都是在此基础上进行优化.所有对JavaScript垃圾回收算法的改进都是基于标记-清除算法的改进,并没有改进标记-清除算法本身和它对“对象是否不再需要”的简化定义。
限制: 那些无法从根对象查询到的对象都将被清除。
当然,在我们的开发实践中很少遇到这种情况,这也是我们忽略内存管理的原因之一。
常见的内存泄漏举例
1.忘记声明的局部变量
function a(){
b=2
console.log(‘b没有被声明!’)
}
b 没被声明,会变成一个全局变量,在页面关闭之前不会被释放.使用严格模式可以避免。
2.闭包带来的内存泄漏
var leaks = (function(){
var leak = ‘xxxxxx’;// 闭包中引用,不会被回收
return function(){
console.log(leak);
}
})()
当然有时候我们是故意让这个变量保存在内存中的,但是要避免无意的时候造成的内存泄漏。
3.移除 DOM 节点时候忘记移除暂存的值
有时候出于优化性能的目的,我们会用一个变量暂存 节点,接下来使用的时候就不用再从 DOM 中去获取.但是在移除 DOM 节点的时候却忘记了解除暂存的变量对 DOM 节点的引用,也会造成内存泄漏。
var element = {
image: document.getElementById(‘image’),
button: document.getElementById(‘button’)
};
document.body.removeChild(document.getElementById(‘image’)); // 如果element没有被回收,这里移除了 image 节点也是没用的,image 节点依然留存在内存中。
与此类似情景还有: DOM 节点绑定了事件, 但是在移除的时候没有解除事件绑定,那么仅仅移除 DOM 节点也是没用的。
4. 定时器中的内存泄漏
var someResource = getData(); setInterval(function() { var node = document.getElementById(‘Node’); if(node) { node.innerHTML = JSON.stringify(someResource)); } }, 1000);
如果没有清除定时器,那么 someResource 就不会被释放,如果刚好它又占用了较大内存,就会引发性能问题. 再提一下 setTimeout ,它计时结束后它的回调里面引用的对象占用的内存是可以被回收的. 当然有些场景 setTimeout 的计时可能很长, 这样的情况下也是需要纳入考虑的。
