JavaScript使用一种称为垃圾收集的技术来管理分配给它的内存。这与C这样的底层语言不一样,C要求使用多少借多少,用完再释放回去。其余语言,好比 Objective-C,实现了一个引用计数系统来辅助完成这些工做,咱们可以了解到有多少个程序块使用了一个特定的内存段,于是能够在不须要时清除这些内存段。javascript
JavaScript是一种高级语言,它通常是经过后台来维护这种计数系统。css
当JavaScript代码生成一个新的内存驻留项时(好比一个对象或函数),系统就会为这个项留出一块内存空间。由于这个对象可能会被传递给不少函数,而且会被指定给不少变量,因此不少代码都会指向这个对象的内存空间。JavaScript会跟踪这些指针,当最后一个指针废弃不用时,这个对象占用的内存会被释放。java
A ---------> B ------------> C
例如对象A有一个属性指向B,而B也有一个属性指向C。即便当前做用域中只有对象A有效,但因为指针的关系全部3个对象都必须保留在内存中。当离开A的当前做用域时(例如代码执行到声明A的函数的末尾处),垃圾收集器就能够释放A占用的内存。此时,因为没有什么指向B,所以B能够释放,最后,C也能够释放。node
然而,当对象间的引用关系变得复杂时,处理起来也会更加困难。web
A ---------> B ------------> C
^、_ _ _ _ _ _ _|
这里,咱们又为对象C添加了一个引用B的属性。在这种状况下,当A释放时,仍然有来自C的指针指向B。这种引用循环须要由JavaScript进行特殊的处理,但必须考虑到整个循环与做用域中的其余变量已经处于隔离状态。chrome
从这里咱们能够看到,闭包问题的本质是做用域的问题,我平时写的闭包大多出如今:浏览器
循环引用
闭包可能会致使在不经意间建立循环引用。由于函数是必须保存在内存中的对象,因此位于函数执行上下文中的全部变量也须要保存在内存中:bash
function outerFn() {
var outerVar = {};
function innerFn() {
console.log(outerVar);
}
outerVar.fn = innerFn;
return innerFn;
};
这里建立了一个名为 outerVar 的对象,该对象在内部函数innerFn()中被引用。而后,为 outerVar 建立了一个指向 innerFn()的属性,以后返回了innerFn()。这样就在 innerFn() 上建立了一个引用outerVar的闭包,而outerVar又引用了innerFn()。闭包
这会致使变量在内存中存在的时间比想象得长,并且又不容易被发现。这还不算完,还有可能会出现比这种状况更隐蔽的引用循环:app
function outerFn() {
var outerVar = {};
function innerFn() {
console.log('hello');
}
outerVar.fn = innerFn;
return innerFn;
};
这里咱们修改了innerFn(),再也不招惹 outerVar。可是,这样作仍然没有断开循环引用。
即便innerFn()再也不勾引 outerVar,outerVar 也仍然位于innerFn()的封闭环境中。因为闭包的缘由,位于 outerFn()中的全部变量都隐含地被 innerFn()所引用。咱们再想想,在 java 中的内部类不也是相似当前状况吗,内部类可以‘看’外部的 this。此时此刻,正如彼时彼刻,竟如此相像。所以,闭包会使意外地建立这些引用循环变得易如反掌。
DOM与JavaScript的循环
虽然我很早就知道闭包,也在调试内存问题时在 chrome F12 里的 profile 是里看到 closure reference
,可是并不清除这个问题的根源。由于上述状况一般不是什么问题,JavaScript可以检测到这些状况并在它们孤立时将其清除。
最近看到关于这个问题的解释:在旧版本IE中存在一种难以处理的循环引用问题。
当一个循环中同时包含DOM元素和常规JavaScript对象时,IE没法释听任何一个对象——由于这两类对象是由不一样的内存管理程序负责管理的。
除非关闭浏览器,不然这种循环在IE中永远得不到释放。为此,随着时间的推移,这可能会致使大量内存被无效地占用。
致使这种循环的一个常见缘由是简单的事件处理:
$(document).ready(function() {
var button = document.getElementById('button-1');
button.onclick = function() {
console.log('hello');
return false;
};
});
当指定单击事件处理程序时,就建立了一个在其封闭的环境中包含button变量的闭包。并且,如今的button也包含一个指向闭包(onclick属性自身)的引用。这样,就致使了在IE中即便离开当前页面也不会释放这个循环。
为了释放内存,就须要断开循环引用,例如关闭窗口,删除onclick属性。另外,也能够像下面这样重写代码来
避免这种闭包:
function hello() {
console.log('hello');
return false;
}
$(document).ready(function() {
var button = document.getElementById('button-1');
button.onclick = hello;
});
由于hello()函数再也不包含 button,引用就成了单向的(从button到hello),不存的循环,因此就不会形成内存泄漏了。
用jQuery化解引用循环
下面,咱们经过常规的jQuery结构来编写一样的代码:
$(document).ready(function() {
var $button = $('#button-1');
$button.click(function(event) {
event.preventDefault();
console.log('hello');
});
});
即便此时仍然会建立一个闭包,而且也会致使同前面同样的循环,但这里的代码却不会使 IE
发生内存泄漏。因为jQuery考虑到了内存泄漏的潜在危害,因此它会手动释放本身指定的全部事件处理程序。只要坚持使用jQuery的事件绑定方法,就无需为这种特定的常见缘由致使的内存泄
漏而担忧。
可是,这并不意味着咱们彻底脱离了险境。当对DOM元素进行其余操做时,仍然要到处留心。只要是将JavaScript对象指定给DOM元素,就可能在旧版本IE中致使内存泄漏。jQuery只是有助于减小发生这种状况的可能性。
有鉴于此,jQuery为咱们提供了另外一个避免这种泄漏的工具。用.data()方法,将信息附加到DOM元素。因为这里的数据并不是直接保存在扩展属性中(jQuery使用一个内部对象并经过它建立的ID来保存这里所说的数
据),所以永远也不会构成引用循环,从而有效回避了内存泄漏问题。
下面附上 jQuery 源码的相关说法: