整理一下「JS避免内存泄漏」,既陌生又熟悉的东西
source link: https://segmentfault.com/a/1190000041252211
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
大家好,我是林三心,上一篇我给大家讲了赠你13张图,助你20分钟打败了「V8垃圾回收机制」,但是关知道回收机制是不行的,V8垃圾回收机制固然很强,但是我们也不能随便就制造很多垃圾让它回收,咱们得在开发中尽量减少垃圾的数量,今天就跟大家讲一讲如何避免JS垃圾过多,内存泄漏吧
为什么要避免
什么是内存泄漏
呢?就是有些理应被回收的垃圾,却没被回收,这就造成了垃圾越积越多。
内存泄漏
,听起来很遥远,但其实离我们很近很近,我们平时都直接或者间接地去接触过它。例如,有时候你的页面,用着用着就卡了起来,而且随着时间的延长,越来越卡,那这个时候,就要考虑是否是内存泄漏
问题了,内存泄漏
是影响用户体验的重大问题,所以平时通过正确的代码习惯去避免它,是非常有必要的。
如何监控内存状况
咱们一直强调内存内存,但是感觉他是很虚无缥缈的东西,至少也得让咱们见见它的真面目吧?
浏览器任务管理器
打开方式:在浏览器顶部右键
,打开任务管理器
:
打开后,咱们看到内存
和JavaScript内存(括号里)
:
内存
:页面里的原始内存,也就是DOM节点
的总占用内存JavaScript内存(括号里)
:是该页面中所有可达对象
的总占用内存
那什么是可达对象
呢?简单说就是:就是从初始的根对象(window或者global)
的指针开始,向下搜索子节点,子节点被搜索到了,说明该子节点的引用对象可达,搜不到,说明该子节点对象不可达。举个例子:
// 可达,可以通过window.name访问 var name = '林三心' function fn () { // 不可达,访问不了 var name = '林三心' }
回到我们的任务管理,此时我们在页面中编写一段代码:
<button id="btn">点击</button> <script> document.getElementById('btn').onclick = function () { list = new Array(1000000) } </script>
点击后,发现内存瞬间上升:
Performance
使用Chrome浏览器的无痕模式
,是为了避免很多其他因素,影响咱们查看内存:
按F12打开调试窗口,选择Performance
咱们就以掘金首页为例吧!点击录制 -> 刷新掘金 -> 点击stop,可以看到以下指标随着时间的上下波动
:
JS Heap
:JS堆Documents
: 文档Nodes
: DOM节点Listeners
: 监听器GPU Memory
: GPU内存
堆快照
,顾名思义,就是将当前某一个页面的堆内存拍下照片
存起来,同一个页面,执行某个操作前,录制堆快照是一个样,有可能执行完后,录制的堆快照又是另外一个样。
还是以掘金首页
为例,可以看到当前页面内存为13.3M
,咱们可以选择Statistics
,查看数组,对象,字符串
等所占内存
上面说了,其实内存泄漏
问题离我们很近,我们可能都直接或者间接造成过。接下来就说说如何避免这个问题吧,可能也是你开发中的坏习惯哦!
减少全局变量
我们在开发中可能遇到过这样的代码,其实我们只是想把a当做局部变量而已,但是忘记写var,let,const
了:
document.getElementById('btn').onclick = function () { // a 未在外部声明过 a = new Array(1000000).fill('Sunshine_Lin') } 上方代码等同于 var a document.getElementById('btn').onclick = function () { a = new Array(1000000).fill('Sunshine_Lin') }
这样有什么坏处呢?咱们前面说过可达性
,在这里就可以解释了。上方代码这么写的话,咱们可以通过window.a
去访问到a
这个全局变量
,所以a是可达的,他不会被当做垃圾去回收,这导致他会一直占用内存而得不到释放,消耗性能,违背了我们的初衷。咱们可以通过堆快照
来验证一下,步骤是:录制 -> 点击按钮 -> 录制
,比较两次的结果,点击后,内存大了4M
,查看Statistics
,发现数组内存大了很多,没得到释放:
那应该怎么改良呢?可以加上定义变量符:
document.getElementById('btn').onclick = function () { let a = new Array(1000000).fill('Sunshine_Lin') }
看看效果,由于局部变量,不可达
,每执行一次函数,就会被回收
,得到释放,所以不会一直占着内存,点击前后的内存是差不多的:
未清除定时器
请看这一段代码,在这段代码中,执行完fn1函数,按理说arr数组会被回收,但是他却回收不了。为什么呢?因为定时器里的a引用着arr,并且定时器不清除的话,a
就不会被回收,a
不回收就会一直引用着arr
,那么arr
肯定也回收不了了。
function fn() { let arr = new Array(1000000).fill('Sunshine_Lin') setInterval(() => { let a = arr }, 1000) } document.getElementById('btn').onclick = function () { fn() }
Performace:录制 -> 手动垃圾回收 -> 连点五次按钮 -> 手动垃圾回收 -> 结束
首尾两次手动垃圾回收,是为了对比首尾两次垃圾内存最低点,而如果没有内存泄漏问题的话,首尾两次最低点应该是相同的,这里可以看到,尾部比首部多出的那部分,就是没有被回收的内存量
上面说了,arr数组
为啥没被回收?是因为定时器
没清除,导致a
一直引用arr
,那怎么解决呢?直接把定时器
清除就行了。
function fn() { let arr = new Array(1000000).fill('Sunshine_Lin') let i = 0 let timer = setInterval(() => { if (i > 5) clearInterval(timer) let a = arr i++ }, 1000) } document.getElementById('btn').onclick = function () { fn() }
再看看Performance
,发现首位两次的内存量是一样的,这就说明正常了
合理使用闭包
咱们来看这一段代码:
function fn1() { let arr = new Array(100000).fill('Sunshine_Lin') return arr } let a = [] document.getElementById('btn').onclick = function () { a.push(fn1()) }
按理说,fn1
执行完后,arr
会被回收,但是在这段代码中,却是没有被回收,为什么呢?因为fn1
执行后,将arr
给return
出去,然后arr
被push进a数组
了,而a数组是个全局变量,a数组
是不会被回收的,那么a数组
里的东西自然也不会被回收,这就导致arr
不会被回收,等到点击越来越多次,不可被回收的arr
就会越来越多,如果a
后来没有被用到,那这些arr
就成无用的垃圾了,咱们可以通过Performance
和堆快照
来验证:
Performace:录制 -> 手动垃圾回收 -> 连点五次按钮 -> 手动垃圾回收 -> 结束
首尾两次手动垃圾回收,是为了对比首尾两次垃圾内存最低点,而如果没有内存泄漏问题的话,首尾两次最低点应该是相同的,这里可以看到,尾部比首部多出的那部分,就是没有被回收的内存量
堆快照:第一次录制 -> 连点5次按钮 -> 第二次录制
会发现,点击前后,内存多了很多,多出来的就是未被回收的内存量
分离DOM
什么叫分离DOM
呢?还是利用代码来说话:
<button id="btn">点击</button> let btn = document.getElementById('btn') document.body.removeChild(btn)
虽然最后把button给删除了,但是因为全局变量btn
对此DOM对象
引用着,导致此DOM
对象一直没有被回收,这个DOM对象
就称为分离DOM
,咱们可以通过堆快照
来验证这个问题,在堆快照里搜索detached(中文意思为:独立,分离)
:
这个问题很好解决,删除button后,顺便把btn设置成null
就行了:
<button id="btn">点击</button> let btn = document.getElementById('btn') document.body.removeChild(btn) btn = null
此时才是真的把button这个DOM,从js中彻底抹去:
我是林三心,一个热心的前端菜鸟程序员。如果你上进,喜欢前端,想学习前端,那咱们可以交朋友,一起摸鱼哈哈,摸鱼群,加我请备注【思否】
Recommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK