Javascript 内存泄漏可能发生的一些场景

729 阅读4分钟

「这是我参与11月更文挑战的第22天,活动详情查看:2021最后一次更文挑战

JavaScript 内存泄漏可能发生的一些场景

Javascript中有一套专门的内存回收机制,虽然存在这样一个机制,但是也可能存在一些特殊的情况,让某些变量没有及时得到释放从而导致了内存泄漏。而内存泄漏又可能是导致性能问题的一个重要原因。

之前面试的时候被问到过相关的问题,而且在开发的时候,避免内存泄漏的发生也是提高代码质量的重要部分。这篇文章主要是梳理一下可能发生内存泄漏的一些场景。

一、什么是内存泄漏?

计算机科学中,内存泄漏指由于疏忽或错误造成程序未能释放已经不再使用的内存。内存泄漏并非指内存在物理上的消失,而是应用程序分配某段内存后,由于设计错误,导致在释放该段内存之前就失去了对该段内存的控制,从而造成了内存的浪费。

简单的来说,内存的生命周期包括分配,使用,释放。如果某些内存在使用完毕之后,没有及时释放(可以供其他程序使用),就可以认为是发生了内存泄漏。

二、内存的管理和垃圾回收机制

说到内存泄漏就必须要先了解Javascript中的内存管理垃圾回收机制。这里就不再赘述了。一方面是感觉了解的不多,怕出现比较大的错误。一方面是MDN和一些优秀的文章也讲的非常清楚了。大家可以自行去了解。这里推荐几篇文章。
MDN_内存管理
前端面试:谈谈 JS 垃圾回收机制
Javascript 垃圾回收机制

三、避免内存泄漏需要关注的代码点

1. DOM元素的循环引用

这个是红宝书里提到的情况。当DOM元素和普通对象产生循环引用的时候,即使DOM元素被移除也得不到回收,从而产生内存泄漏。

var element = document.getElementById("hello")
var obj = new Object()
obj.element = element
element.obj = obj

解决的方法是在不使用的时候主动断开变量和对象之间的引用。

obj.element = null
element.obj = null

2. 未释放的引用

这个也是红宝书上提到的情况。

一旦数据不再有用,最好通过将其设置为null来释放其引用————这个方法叫做解除引用。

不过要注意的是,解除一个值的引用不代表自动回收该值所占用的内存。真正目的是让值脱离执行环境,以便垃圾收集器下次执行的时候将其回收。

2. 全局变量

声明变量如果没有使用var关键字,会默认挂载到window对象上,这样就创建的意外的全局变量。不过ES6中提出了let 和 const,而且还有eslint之类的检查工具,这种情况基本不会再犯了。

3. 事件监听函数

对于DOM和BOM中的各类事件监听。DOM中原生的addEventLisner函数及引入的第三方库中封装的事件监听。BOM对象的事件监听,比如webSocket的监听事件。

在不需要监听的时候可以及时移除监听器。

4. 计时器

忘记清理无用的计时器是常见的错误之一。

5. ES6中的Set和Map结构

ES6中Set和Map结构中存储的数据,仅仅是将值设置为null,还是会存在内存泄漏。 还要将元素从Set和Map中移除才没有内存泄漏。

举个例子

有内存泄漏:

let map = new Set();
let value = { test: 22};
map.add(value);

value= null;

没有内存泄漏:

let map = new Set();
let value = { test: 22};
map.add(value);

map.delete(value);
value = null;

当然ES6也引入了对应的WeakSet和WeakMap结构,比较简单的办法就是采用这两种数据结构,具体情况还要分析使用场景。

6. 在vue框架中经常忘记的destroy钩子

因为平常用的比较多的框架是vue,所以说一说vue相关的。 可能大家开发的时候都是created,mounted钩子用的比较多,destroy钩子不是经常用。但这个确实可能导致内存泄漏的情况。

如果在mounted/created 钩子中进行了如下操作

  • 绑定了DOM/BOM 对象中的事件
  • 初始化第三方库
  • 使用了定时器
  • 监听事件($on)
  • ... 等等

都需要在beforeDestroy 中做对应销毁和解绑处理

Vue官网上也有介绍避免内存泄露相关的点 cn.vuejs.org/v2/cookbook…




参考文章:
MDN_内存管理
深入了解 JavaScript 内存泄露
Javascript 垃圾回收机制