在程序运行过程当中再也不用到的内存,没有及时释放,会出现内存泄漏(memory leak),会形成系统内存的浪费,致使程序运行速度减慢甚至系统崩溃等严重后果。javascript
而内存泄漏是每一个开发人员最终必须面对的问题。 即便使用内存管理语言,好比C语言有着malloc()
和 free()
这种低级内存管理语言也有可能出现泄露内存的状况。html
这很麻烦,因此为了减轻编程中的负担,大多数语言提供了自动内存管理,这被称为"垃圾回收机制"(garbage collector)。java
如今各大浏览器一般采用的垃圾回收有两种方法:标记清除(mark and sweep)、引用计数(reference counting)。node
一、标记清除git
这是javascript中最经常使用的垃圾回收方式。程序员
工做原理:当变量进入执行环境时,将这个变量标记为“进入环境”。当变量离开环境时,则将其标记为“离开环境”。标记“离开环境”的就回收内存。github
工做流程:算法
二、引用计数编程
工做原理:跟踪记录每一个值被引用的次数。数组
工做流程:
但若是一个值再也不须要了,引用数却不为0,垃圾回收机制没法释放这块内存,会致使内存泄漏。
var arr = [1, 2, 3];
console.log('hello miqilin');
复制代码
上面代码中,数组[1, 2, 3]
会占用内存,赋值给了变量arr
,所以引用次数为1。尽管后面的一段代码没有用到arr
,它仍是会持续占用内存。
若是增长一行代码,解除arr对[1, 2, 3]
引用,这块内存就能够被垃圾回收机制释放了。
var arr = [1, 2, 3];
console.log('hello miqilin');
arr = null;
复制代码
上面代码中,arr
重置为null
,就解除了对[1, 2, 3]
的引用,引用次数变成了0,内存就能够释放出来了。
所以,并非说有了垃圾回收机制,程序员就无事一身轻了。你仍是须要关注内存占用:那些很占空间的值,一旦再也不用到,你必须检查是否还存在对它们的引用。若是是的话,就必须手动解除引用。
接下来,我将介绍四种常见的JavaScript 内存泄漏及如何避免。目前水平有限,借鉴了国外大牛的文章了解这几种内存泄漏,原文连接:blog.sessionstack.com/how-javascr…
1.意外的全局变量
未定义的变量会在全局对象建立一个新变量,对于在浏览器的状况下,全局对象是window
。 看如下代码:
function foo(arg) {
bar = "this is a hidden global variable";
}
复制代码
函数foo
内部使用var
声明,实际上JS会把bar
挂载在全局对象上,意外建立一个全局变量。等同于:
function foo(arg) {
window.bar = "this is an explicit global variable";
}
复制代码
在上述状况下, 泄漏一个简单的字符串不会形成太大的伤害,但它确定会更糟。
另外一种能够建立偶然全局变量的状况是this
:
function foo() {
this.variable = "potential accidental global";
}
// Foo called on its own, this points to the global object (window)
// rather than being undefined.
foo();
复制代码
解决方法:
在 JavaScript 文件头部加上 'use strict'
,使用严格模式避免意外的全局变量,此时上例中的this
指向undefined
。若是必须使用全局变量存储大量数据时,确保用完之后把它设置为 null
或者从新定义。
2.被遗忘的计时器或回调函数
在JavaScript中使用setInterval
很是常见。
var someResource = getData();
setInterval(function() {
var node = document.getElementById('Node');
if(node) {
// Do stuff with node and someResource.
node.innerHTML = JSON.stringify(someResource));
} }, 1000);
复制代码
上面的代码代表,在节点node
或者数据再也不须要时,定时器依旧指向这些数据。因此哪怕当node
节点被移除后,interval
仍旧存活而且垃圾回收器没办法回收,它的依赖也没办法被回收,除非终止定时器。
var element = document.getElementById('button');
function onClick(event) {
element.innerHtml = 'text';
}
element.addEventListener('click', onClick); // Do stuff
element.removeEventListener('click', onClick);
element.parentNode.removeChild(element);
// Now when element goes out of scope,
// both element and onClick will be collected even in old browsers that don't
// handle cycles well.
复制代码
对于上面观察者的例子,一旦它们再也不须要(或者关联的对象变成不可达),明确地移除它们很是重要。其中IE 6 是没法处理循环引用的。由于老版本的 IE 是没法检测 DOM 节点与 JavaScript 代码之间的循环引用,会致使内存泄漏。
可是,现代的浏览器(包括 IE 和 Microsoft Edge)使用了更先进的垃圾回收算法(标记清除),已经能够正确检测和处理循环引用了。即回收节点内存时,没必要非要调用removeEventListener
了。
诸如jQuery之类的框架和库在处理节点以前会删除侦听器(当使用它们的特定API时)。 这由库内部处理,并确保不会产生任何泄漏,即便在有问题的浏览器(如旧版Internet Explorer)下运行也是如此。
3.闭包
JavaScript 开发的一个关键知识是闭包:这是一个内部函数,它能够访问外部(封闭)函数的变量。因为 JavaScript 运行时的实现细节,用下边这种方式可能会形成内存泄漏:
var theThing = null;
var replaceThing = function () {
var originalThing = theThing;
var unused = function () {
if (originalThing)
console.log("hi");
};
theThing = {
longStr: newArray(1000000).join('*'),
someMethod: function () {
console.log(someMessage);
}
};
};
setInterval(replaceThing, 1000);
复制代码
每次调用replaceThing
,theThing
获得一个包含一个大数组和一个新闭包(someMethod
)的新对象。同时,变量unused
是一个引用originalThing
的闭包(先前的replaceThing
又调用了theThing
)。someMethod
能够经过theThing
使用,someMethod
与unused
分享闭包做用域,尽管unused
从未使用,它引用的originalThing
迫使它保留在内存中(防止被回收)。须要记住的是一旦一个闭包做用域被同一个父做用域的闭包所建立,那么这个做用域是共享的。
全部这些均可能致使严重的内存泄漏。当上面的代码片断一次又一次地运行时,你能够看到内存使用量的急剧增长。当垃圾收集器运行时,也不会减小。一个连接列表闭包被建立(在这种状况下 theThing
变量是根源),每个闭包做用域对打数组进行间接引用。
解决方法:
在 replaceThing
的最后添加 originalThing = null
。将全部联系都切断。
4.脱离 DOM 的引用
若是把DOM 存成字典(JSON 键值对)或者数组,此时,一样的 DOM 元素存在两个引用:一个在 DOM 树中,另外一个在字典中。若是在未来某个时候您决定删除这些行,则须要使两个引用都没法访问,都清除掉。
var elements = {
button: document.getElementById('button'),
image: document.getElementById('image'),
text: document.getElementById('text')
};
function doStuff() {
image.src = 'http://some.url/image';
button.click();
console.log(text.innerHTML);
// Much more logic
}
function removeButton() {
// The button is a direct child of body.
document.body.removeChild(document.getElementById('button'));
// At this point, we still have a reference to #button in the global
// elements dictionary. In other words, the button element is still in
// memory and cannot be collected by the GC.
}
复制代码
若是代码中保存了表格某一个<td>
的引用。未来决定删除整个表格的时候,直觉认为 GC 会回收除了已保存的<td>
之外的其它节点。实际状况并不是如此:此<td>
是表格的子节点,子元素与父元素是引用关系。因为代码保留了<td>
的引用,致使整个表格仍待在内存中。因此保存 DOM 元素引用的时候,要当心谨慎。
在局部做用域中,等函数执行完毕,变量就没有存在的必要了,js垃圾回收机制很快作出判断而且回收,可是全局变量何时须要自动释放内存空间则很难判断,所以在咱们的开发中,须要尽可能避免使用全局变量。
咱们在使用闭包的时候,就会形成严重的内存泄漏,由于闭包的缘由,局部变量会一直保存在内存中,因此在使用闭包的时候,要多加当心。
若是有别的关于内存泄漏好的资源,能够分享给我嘛谢谢了~
本人Github连接以下,欢迎各位Star