「这是我参与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…