关于前端:整理一下JS避免内存泄漏既陌生又熟悉的东西

5次阅读

共计 3611 个字符,预计需要花费 10 分钟才能阅读完成。

前言

大家好,我是林三心,上一篇我给大家讲了赠你 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 执行后,将 arrreturn进来,而后 arrpush 进 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 中彻底抹去:

参考资料

  • 淘宝前端是怎么做优化?如何高效书写 JavaScript?进步 JS 性能有哪些骚操作?
  • 一文带你理解如何排查内存透露导致的页面卡顿景象

结语

我是林三心,一个热心的前端菜鸟程序员。如果你上进,喜爱前端,想学习前端,那咱们能够交朋友,一起摸鱼哈哈,摸鱼群,加我请备注【思否】

正文完
 0