这一章节给你们介绍的知识点相对比较简单, 可是倒是很是重要的. 并且也是在面试过程当中常常会被问到的一部份内容.前端
经过这次阅读你能够学习到:web
其实在实际开发中, 咱们很容易不经意的就写出内存泄露的代码, 好比如下几种状况可能都是你遇到过的.面试
当咱们在一个函数中给一个变量赋值可是却没有声明它时:算法
function fn () {
a = "Actually, I'm a global variable"
}
复制代码
此时变量a
至关因而window
对象下的一个变量:数组
function fn () {
window.a = "Actually, I'm a global variable"
}
复制代码
而以前咱们已经说了全局变量是很难被垃圾回收器回收的, 因此要是有这种意外的全局变量应该要避免.浏览器
this
建立的变量还有一种状况是这样的:闭包
function fn () {
this.a = "Actually, I'm a global variable"
}
fn();
复制代码
咱们知道, 这里this
的指向是window
, 所以此时建立的a
变量也会被挂载到window
对象下.编辑器
避免此状况的解决办法是在 JavaScript 文件头部或者函数的顶部加上 'use strict'
, 开启严格模式, 使得this
的指向为undefined
, 这样就能够避免了.ide
固然若是你必须使用全局变量存储大量数据时,确保用完之后把它设置为 null 或者从新定义。函数
”
当咱们在代码中使用定时器也有可能会形成内存泄露:
var serverData = loadData()
setInterval(function() {
var renderer = document.getElementById('renderer')
if(renderer) {
renderer.innerHTML = JSON.stringify(serverData)
}
}, 5000)
复制代码
上面的例子🌰中, 节点renderer
引用了serverData
.在节点renderer
或者数据再也不须要时,定时器依旧指向这些数据。因此哪怕当renderer
节点被移除后,interval 仍旧存活而且垃圾回收器没办法回收,它的依赖也没办法被回收,除非终止定时器。
还有一个就是关于观察者模式的案例:
var btn = document.getElementById('btn');
function onClick (element) {
element.innerHTMl = "I'm innerHTML"
}
btn.addEventListener('click', onClick);
复制代码
对于上面观察者的例子,一旦它们再也不须要(或者关联的对象变成不可达),明确地移除它们很是重要。老的 IE 6 是没法处理循环引用的。由于老版本的 IE 是没法检测 DOM 节点与 JavaScript 代码之间的循环引用,会致使内存泄漏。
可是,现代的浏览器(包括 IE 和 Microsoft Edge)使用了更先进的垃圾回收算法(标记清除),已经能够正确检测和处理循环引用了。即回收节点内存时,没必要非要调用 removeEventListener 了。
这种形成内存泄露的缘由简单来讲就是:
若是把DOM 存成字典(JSON 键值对)或者数组,此时,一样的 DOM 元素存在两个引用:一个在 DOM 树中,另外一个在字典中。那么未来须要把两个引用都清除。
好比下面这个例子:
// 在对象中引用DOM
var elements = {
btn: document.getElementById('btn')
}
function doSomeThing () { elements.btn.click(); }
复制代码function removeBtn () { // 将body中的btn移除, 也就是移除 DOM树中的btn document.body.removeChild(document.getElementById('button')); // 可是此时全局变量elements仍是保留了对btn的引用, btn仍是存在于内存中,不能被GC回收 } 复制代码
上面👆这种状况, 能够手动将引用给清除: elements.btn = null
.
还有一种状况就是咱们以前提到过的闭包. 也就是局部变量销毁时, 闭包的这种状况.
首先让咱们明确一点: 闭包的关键就是匿名函数可以访问父级做用域中的变量.
来看一个简单的例子🌰:
function fn () {
var a = "I'm a";
return function () {
console.log(a);
};
}
复制代码
由于变量a
被fn()
函数内的匿名函数所引用, 所以这种变量是不会被回收的.
那就有人问了, 即便这样会形成什么不妥吗? 在上面👆这个案例中固然看不出有什么. 如果将状况变得复杂一些呢?
var globalVar = null; // 全局变量
var fn = function () {
var originVal = globalVar; // 局部变量
var unused = function () { // 未使用的函数
if (originVal) {
console.log('call')
}
}
globalVar = {
longStr: new Array(1000000).join('*'),
someThing: function () {
console.log('someThing')
}
}
}
setInterval(fn, 100);
复制代码
先请你花上一分钟看看上面的案例, 你会发现:
fn
函数的时候都会产生一个新的对象
originVal
;
unused
是一个引用了
originVal
的闭包;
unused
虽然未被使用, 可是它引用的
originVal
迫使它留在内存中, 并不会被回收.
解决办法是: 能够在fn
的最底部, 将originVal
设置成null
.
上面👆介绍了这么多种可能会形成内存泄露的状况, 那么有没有什么实际的办法让咱们看到内存泄露的表现呢?
固然是有的. 如今经常使用的是如下2种方式:
Chrome
浏览器的控制台
Performance
或
Memory
Node
提供的
process.memoryUsage
方法
Chrome
浏览器的控制台Performance
或Memory
Chrome 浏览器查看内存占用,按照如下步骤操做。
Mac
快捷键
option+command+i
);
Performance
面板(不少教材中用的是
Timeline
面板, 不知道是否是版本的缘由);
Memory
, 而后点击左上角的小黑点
Record
开始录制;
Stop
结束录制, 面板上就会显示这段时间的内存占用状况。
若是内存的使用状况一直在作增量, 那么就是内存泄露了:
或者你可使用我在《记录一次定时器及闭包问题形成的内存泄漏》中的方法进行检查.
Node
提供的process.memoryUsage
方法另外一个就是Node
提供的process.memoryUsage
方法, 这一块我用的不是不少, 这里就贴上教材:
console.log(process.memoryUsage());
// { rss: 27709440,
// heapTotal: 5685248,
// heapUsed: 3449392,
// external: 8772 }
复制代码
process.memoryUsage返回一个对象,包含了 Node 进程的内存占用信息。该对象包含四个字段,单位是字节,含义以下:
rss(resident set size):全部内存占用,包括指令区和堆栈。
heapTotal:"堆"占用的内存,包括用到的和没用到的。
heapUsed:用到的堆的部分。
external: V8 引擎内部的 C++ 对象占用的内存。
复制代码
判断内存泄露, 是看heapUsed
字段.
总的来讲, 常见的内存泄露包括:
如何避免内存泄露:
关于JavaScript
进阶内存堆栈的内容就告一段落了, 总共是有五篇文章. 在文章中我也是尽可能用比较通俗的语言来让进行讲解, 但愿你们都能搞懂.
若是你们喜欢霖呆呆的文章的话, 还请帮我一个小忙, 关注一波我最近才开始捣鼓的公众号, 我会在上面不按期的发一些关于前端方面的原创文章, 一块儿学习, 一块儿进步😊.