垃圾回收机制
JavaScript具有自动垃圾回收机制,来判断哪些变量不再需要,释放他所占的内存。
1. 变量的生命周期
在创建时,JavaScript会自动为他分配内存,然后垃圾回收机制会按照固定的时间间隔对这些变量进行评估,看他是否有效。无效则释放其内存。
2. 常见的垃圾回收方式:
引用计数
跟踪每个值被引用的次数,当引用次数为0时,回收其所占内存空间。当声明一个变量并将一个引用类型的值赋给该变量,则这个值的引用次数就是1,如果同一个值又被赋给另一个变量,该值的引用次数加1,如果包含对这个值的引用的变量又取得另一个值,这个值的引用次数减1。
出现的问题: 循环引用,导致引用次数永远不为0,无法回收所占用的内存空间,导致内存泄漏。
解决:手工解除循环引用,直接置为null。
注意:IE中有一部分对象并不是原生JavaScript对象。例如,其BOM和DOM中的对象就是使用C++以COM(Component Object Model,组件对象)对象的形式实现的,而COM对象的垃圾回收器就是采用的引用计数的策略。所以IE中只要有COM对象,就会有循环引用问题。标记清除
1.比如,当变量进入执行环境,将变量标记为“进入环境”,当变量离开环境时,将其标记为“离开环境”。我们不能释放进入环境的变量,因为有可能会用到。
2.垃圾回收器会在运行的时候给存储在内存中的所有变量加上标记,然后去掉环境中的变量以及被环境中变量所引用的变量(闭包),在这些完成之后仍存在标记的就是要删除的变量了,因为环境中的变量已经无法访问到这些变量了,然后垃圾回收器会回收这些带有标记的变量机器所占空间。
出现的问题:就是在清除之后,内存空间是不连续的,即出现了内存碎片,标记-整理方法可以有效地解决这个问题,整理的过程就是将不连续的内存向一端复制,使不连续的内存连续起来。
内存泄露
内存泄漏:没有及时的释放不再需要的内存。我们已经无法再通过js代码来引用到某个对象,但垃圾回收器却认为这个对象还在被引用,因此在回收的时候不会释放它。
内存泄露的形式
- 意外的全局变量
对未声明的变量的引用是在全局对象内创建一个新的变量
function foo(arg) {
bar = "";
}
foo();
bar的作用域是window,所以函数foo执行完后,bar不会被回收。
- 闭包
闭包可以导致内存泄漏是因为内部方法保持一个对外部方法变量的引用,所以尽管方法返回了,内部方法还可以继续访问在外部方法中定义的私有变量。
闭包经常会在不知不觉中创建出循环引用,所以它对内存泄露有不可推卸的责任
function setHandler() {
const ele = document.getElementById('id');
ele.onclick = function() {
alert(ele.id);
};
}
创建一个作为ele元素事件处理程序的闭包。而闭包又创建了一个循环引用。
- 被遗忘的计时器或回调函数
var someResource = getData();
setInterval(function() {
var node = document.getElementById('Node');
if(node) {
node.innerHTML = JSON.stringify(someResource));
}
}, 1000);
在setInterval
没有结束前,回调函数里的变量以及回调函数本身都无法被回收。那什么才叫结束呢?就是调用了 clearInterval
。如果回调函数内没有做什么事情,并且也没有被 clear 掉的话,就会造成内存泄漏。setTiemout
也会有同样的问题,需要调用clearTimeout
。
- 脱离 DOM 的引用
保存 DOM 节点内部数据结构很有用。假如你想快速更新表格的几行内容,把每一行 DOM 存成字典(JSON 键值对)或者数组很有意义。此时,同样的 DOM 元素存在两个引用:一个在 DOM 树中,另一个在字典中。将来你决定删除这些行时,需要把两个引用都清除。
参考:
https://www.ibm.com/developerworks/cn/web/wa-jsmemory/index.html
http://bubkoo.com/2015/01/31/understanding-and-solving-internet-explorer-leak-patterns/
https://jinlong.github.io/2016/05/01/4-Types-of-Memory-Leaks-in-JavaScript-and-How-to-Get-Rid-Of-Them/
https://segmentfault.com/a/1190000007616791#articleHeader4