深入了解 JavaScript 内存泄漏( 三 )


但是如果 reverseName 没有被调用,在当前执行环境未结束的情况下,严格来说,这样是有内存泄漏的,name 变量是被 closure 返回的函数调用了,但是返回的函数没被使用,在这个场景下 name 就属于垃圾内存 。name 不是必须的,但是还是占用了内存,也不可被回收 。
当然这种也是极端情况,很少人会犯这种低级错误 。这个例子可以让我们更清楚的认识内存泄漏 。
DOM 的引用
每个页面上的 DOM 都是占用内存的,建设有一个页面 A 元素,我们获取到了 A 元素 DOM 对象,然后赋值到了一个变量(内存指向是一样的),然后移除了页面上的 A 元素,如果这个变量由于其他原因没有被回收,那么就存在内存泄漏,如下面的例子:
class Test { constructor() { this.elements = { button: document.querySelector('#button'), div: document.querySelector('#div') } } removeButton() { document.body.removeChild(this.elements.button); // this.elements.button = null } } const test = new Test(); test.removeButton();
上面的例子 button 元素虽然在页面上移除了,但是内存指向换成了 this.elements.button,内存占用还是存在的 。所以上面的代码还需要这么写:this.elements.button = null,手动释放内存 。
如何发现内存泄漏
内存泄漏时,内存一般都是周期性的增长,我们可以借助谷歌浏览器的开发者工具进行判断 。
这里针对下面的例子进行一步步的的排查和找到问题点:
运行 停止
确实是否是内存泄漏问题
访问上面的代码页面,打开开发者工具,切换至 Performance 选项,勾选 Memory 选项 。
在页面上点击运行按钮,然后在开发者工具上面点击左上角的录制按钮,10 秒后在页面上点击停止按钮,5 秒停止内存录制 。得到内存走势如下:

深入了解 JavaScript 内存泄漏

文章插图
 
由上图可知,10 秒之前内存周期性增长,10 秒后点击了停止按钮,内存平稳,不再递增 。我们可以使用内存走势图判断是否存在内存泄漏 。
查找内存泄漏的位置
上一步确认内存泄漏问题后,我们继续利用开发者工具进行问题查找 。
访问上面的代码页面,打开开发者工具,切换至 Memory 选项 。页面上点击运行按钮,然后点击开发者工具左上角的录制按钮,录制完成后继续点击录制,直到录制完成三个为止 。然后点击页面上的停止按钮,在连续录制三次内存(不要清理之前的录制) 。
深入了解 JavaScript 内存泄漏

文章插图
 
从这里也可以看出,点击运行按钮之后,内存在不断的递增 。点击停止按钮之后,内存就平稳了 。虽然我们也可以用这种方式来判断是否存在内存泄漏,但是没有第一步的方法便捷,走势图也更加直观 。
然后第二步的主要目的是为了记录 JavaScript 堆内存,我们可以看到哪个堆占用的内存更高 。
深入了解 JavaScript 内存泄漏

文章插图
 
从内存记录中,发现 array 对象占用最大,展开后发现,第一个 object elements 占用最大,选择这个 object elements 后可以在下面看到 newArr 变量,然后点击后面的高亮链接,就可以跳转到 newArr 附近 。

【深入了解 JavaScript 内存泄漏】


推荐阅读