前言:这篇文章的主要内容由翻译而来,原文连接。可是大致内容与原文不尽相同,删除了一些内容,同时新增部份内容。因为本文大部份内容是翻译而来,如有理解不当之处还望谅解并指出,我会尽快进行修改。(心里:若是有什么不对的地方还但愿你们指出,反正我也不会改 。玩笑话玩笑话 别当真!)javascript
在一些语言中,开发人员须要手动的使用原生语句来显示的分配和释放内存。可是在许多高级语言中,这些过程都会被自动的执行。在JavaScript中,变量(对象,字符串,等等)建立的时候为其分配内存,当再也不被使用的时候会“自动地”释放这些内存,这个过程被称为垃圾回收。这个看似“自动的”释放资源的本质是一个混乱的来源,给JavaScript(和其余高等级语言)开发者能够不去关心内存管理的错误印象。这是一个很大的错误。java
内存泄漏(Memory Leak)是指程序中己动态分配的堆内存因为某种缘由程序未释放或没法释放,形成系统内存的浪费,致使程序运行速度减慢甚至系统崩溃等严重后果。算法
内存泄漏缺陷具备隐蔽性、积累性的特征,比其余内存非法访问错误更难检测。由于内存泄漏的产生缘由是内存块未被释放,属于遗漏型缺陷而不是过错型缺陷。此外,内存泄漏一般不会直接产生可观察的错误症状,而是逐渐积累,下降系统总体性能,极端的状况下可能使系统崩溃。编程
不管使用哪种编程语言,内存的生命周期几乎老是如出一辙的 分配内存、使用内存、释放内存。 在这里咱们主要讨论内存的回收。canvas
这是最简单的垃圾回收算法。一个对象在没有被其余的引用指向的时候就被认为“可回收的”。 api
对JS引用类型不熟悉的请先百度引用类型,理解了值类型(基本类型)和引用类型以后才能理解下面的代码浏览器
var obj1 = {
obj2: {
x: 1
}
};
//2个对象被建立。 obj2被obj1引用,而且做为obj1的属性存在。这里并无能够被回收的。
//obj1和obj2都指向了{obj2: {x: 1}}这个对象,这个示例中用`原来的对象`来表示这个对象。
var obj3 = obj1; //obj3也引用了obj1指向的对象。
obj1 = 1; // obj1不引用原来的对象了。此时原来的对象只有obj3在引用。
var obj4 = obj3.obj2; //obj4引用了obj3对象的obj2属性,
//此时obj2对象有2个引用,一个是做为obj3的一个属性,一个是做为obj4变量。
obj3 = 1;
// 咦,obj1原来对象只有obj3在引用,如今obj3也没用在引用了。
// obj1原来的对象就沦为了一只单身狗,因而乎抓狗大队就来带走了它。(好吧、其实内存就能够被回收了)。
// 然而 obj2对象依然有人爱(被obj4引用)。因此obj2的内存就不会被垃圾回收。
obj4 = null;
// obj2心里在呐喊:小姐姐不要离开我 QOQ。如今obj2也没有被引用了,引用计数就是0
也就是能够被回收了。
复制代码
简而言之~,若是内存有人爱,那就不会被回收。若是是单身狗的话,[手动滑稽]。bash
循环引用会形成麻烦session
引用计数在涉及循环引用的时候有一个缺陷。在下面的例子中,建立了2个对象,而且相互引用,这样建立了一个循环。所以他们其实是无用的,能够被释放。然而引用计数算法考虑到2个对象中的每个至少被引用了一次,所以都不能够被回收。 闭包
function f() {
var o1 = {};
var o2 = {};
o1.p = o2;
o2.p = o1;
}
f();
复制代码
单身狗内心千万头草泥马在奔腾(我特么也会本身牵本身手啊,我也会伪装情侣拍照啊)
别觉得你伪装不是单身狗就拿你没办法了,这个算法肯定了对象是否能够被达到。 这个算法包含了如下步骤:
window
对象能够做为一个'根'在以前的例子中,虽然两个变量相互引用,但在函数执行完以后,这个两个变量都没有被window
对象上的任何对象所引用。所以,他们会被认为不可到达的。
1:全局变量 JavaScript用一个有趣的方式管理未被声明的变量:对未声明的变量的引用在全局对象里建立一个新的变量。在浏览器的状况下,这个全局对象是window
。换句话说:
function foo(arg) {
bar = 'some text';
}
//等同于
function foo(arg) {
window.bar = 'some text';
}
复制代码
若是bar
被假定只在foo
函数的做用域里引用,可是你忘记了使用var
去声明它,一个意外的全局变量就被声明了。 在这个例子里,泄漏一个简单的字符并不会形成很大的伤害,可是它确实有可能变得更糟。 有时有会经过this
来建立意外的全局变量。
为了防止这些问题发生,能够在你的JaveScript文件开头使用
'use strict'
;。这个可使用一种严格的模式解析JavaScript来阻止意外的全局变量。
若是有时全局变量被用于暂时储存大量的数据或者涉及到的信息,那么在使用完以后应该指定为null或者从新分配。
2:被遗忘的定时器或者回调 仍是来个栗子吧,定时器可能会产生对再也不须要的DOM节点或者数据的引用。
var serverData = loadData();
setInterval(function() {
var renderer = document.getElementById('renderer');
if(renderer) {
renderer.innerHTML = JSON.stringify(serverData);
}
}, 5000); //每五秒会执行一次
复制代码
renderer
对象在未来有可能被移除,让interval
没有存在的意义。然而当处理器interval
仍然起做用时,renderer
并不能被回收(interval
在对象被移除时须要被中止),若是interval
不能被回收,它的依赖也不可能被回收。这就意味着serverData
,大概保存了大量的数据,也不可能被回收。 现在,大部分的浏览器都能并且会在对象变得不可到达的时候回收观察处理器,甚至监听器没有被明确的移除掉。在对象被处理以前,最好也要显式地删除这些观察者。
var element = document.getElementById('launch-button');
var counter = 0;
function onClick(event) {
counter++;
element.innerHtml = 'text ' + counter;
}
element.addEventListener('click', onClick);
// 作一些其余的事情
element.removeEventListener('click', onClick);
element.parentNode.removeChild(element);
复制代码
现在,如今的浏览器(包括IE和Edge)使用现代的垃圾回收算法,能够当即发现并处理这些循环引用。换句话说,在一个节点删除以前也不是必需要调用removeEventListener。 框架和插件例如jQuqery在处理节点(当使用具体的api的时候)以前会移除监听器。这个是插件内部的处理能够确保不会产生内存泄漏,甚至运行在有问题的浏览器上(哈哈哈 说的就是IE6)。
3: 闭包 闭包是javascript开发的一个关键方面,一个内部函数使用了外部(封闭)函数的变量。因为JavaScript运行的细节,它可能如下面的方式形成内存泄漏:
var theThing = null;
var replaceThing = function () {
var originalThing = theThing;
var unused = function () {
if (originalThing) console.log('hi') //引用了originalThing
};
theThing = {
longStr: new Array(1000000).jojin('*'),
someMethod: function (){
console.log('message');
}
};
};
setInterval(replaceThing,1000);
复制代码
这些代码作了一件事情,每次relaceThing
被调用,theThing
得到一个包含大量数据和新的闭包(someMethod
)的对象。同时,变量unused
引用了originalThing
(theThing
是上一次函数被调用时产生的)。已经有点困惑了吧?最重要的事情是一旦为同一父域中的做用域产生闭包,则该做用域是共享的。
在这个案例中,someMethod
和unused
共享闭包做用域,unused
引用了originalThing
,这阻止了originalThing
的回收,尽管unused
不会被使用,可是someMethod
依然能够经过theThing
来访问replaceThing
做用域外的变量(例如某些全局的)。
4:来自DOM的引用 在你要重复的操做DOM节点的时候,存储DOM节点是十分有用的。可是在你须要移除DOM节点的时候,须要确保移除DOM tree和代码中储存的引用。
var element = {
image: document.getElementById('image'),
button: document.getElementById('button')
};
//Do some stuff
document.body.removeChild(document.getElementById('image'));
//这个时候 虽然从dom tree中移除了id为image的节点,可是还保留了一个对该节点的引用。因而image仍然不能被回收。
复制代码
当涉及到DOM树内部或子节点时,须要考虑额外的考虑因素。例如,你在JavaScript中保持对某个表格的特定单元格的引用。有一天你决定从DOM中移除表格可是保留了对单元格的引用。你也许会认为除了单元格其余的都会被回收。实际并非这样的:单元格是表格的一个子节点,子节点保持了对父节点的引用。确切的说,JS代码中对单元格的引用形成了整个表格被留在内存中了,因此在移除有被引用的节点时候要移除其子节点。