关于 “内存泄漏”

103 阅读3分钟

什么是内容泄漏?

简单的说,内存泄漏就是指任何对象在我们使用完,不需要或者是不再拥有它之后仍然存在。

什么情况或者操作会导致内存泄漏呢?

  1. JavaScript 是一种垃圾回收语言。其垃圾回收器会定期扫描对象,并计算了每个对象的其他对象的数量。如果一个对象的引用数量为零(即没有其他对象引用过该对象),或对该对象的唯一引用是循环的,那么该对象的内存即可回收。
  2. setTimeout的第一个参数使用字符串而非函数的,会引发内存泄漏。 3.闭包,控制台日志,循环(即在两个对象彼此引用且彼此保留时,就会产生一个循环)

解决方式有哪些呢?

1. 意外的全局变量(global variables)

因为,JavaScript 处理未定义变量的方式比较宽松:未定义的变量会在全局对象创建一个新变量。我们在函数里直接引用一个未命名的变量就会导致在全局创建一个变量,或者造成全局污染。或者我们在一个普通函数里用this时也会创建意外的全局对象。

解决方法 :

  1. 可以在js文件的开头添加‘use strict’,使用严格模式,这样在严格模式下可以防止意外的全局变量。

  2. 在使用完该变量后,对其赋值为null或者重新分配。

2.被忘记的Timers或者callbacks

在我们使用完响应的函数方法后,比如定时器,它可能还会引用不再需要的节点或数据。所以在使用完不在需要后,及时清除定时器。

3. 闭包

闭包是 JavaScript 开发的一个关键方面:匿名函数可以访问父级作用域的变量。

代码示例:

image.png

代码片段做了一件事情:每次调用 replaceThing ,theThing 得到一个包含一个大数组和一个新闭包(someMethod)的新对象。同时,变量 unused 是一个引用 originalThing 的闭包(先前的 replaceThing 又调用了 theThing )。思绪混乱了吗?最重要的事情是,闭包的作用域一旦创建,它们有同样的父级作用域,作用域是共享的。someMethod 可以通过 theThing 使用,someMethodunused 分享闭包作用域,尽管 unused从未使用,它引用的 originalThing 迫使它保留在内存中(防止被回收)。当这段代码反复运行,就会看到内存占用不断上升,垃圾回收器(GC)并无法降低内存占用。本质上,闭包的链表已经创建,每一个闭包作用域携带一个指向大数组的间接的引用,造成严重的内存泄露。

解决问题的方法就是:在 replaceThing 的最后添加 originalThing = null

4. 脱离DOM的引用

有时,保存 DOM 节点内部数据结构很有用。假如你想快速更新表格的几行内容,把每一行 DOM 存成字典(JSON 键值对)或者数组很有意义。此时,同样的 DOM 元素存在两个引用:一个在 DOM 树中,另一个在字典中。将来你决定删除这些行时,需要把两个引用都清除。

此外还要考虑 DOM 树内部或子节点的引用问题。假如你的 JavaScript 代码中保存了表格某一个 <td> 的引用。将来决定删除整个表格的时候,直觉认为 GC 会回收除了已保存的 <td> 以外的其它节点。实际情况并非如此:此<td> 是表格的子节点,子元素与父元素是引用关系。由于代码保留了 <td> 的引用,导致整个表格仍待在内存中。保存 DOM 元素引用的时候,要小心谨慎。