导致内存泄漏的因素
一、全局变量
因为全局变量不被js垃圾回收机制所回收,所以在使用全局变量时要小心。避免在想使用局部变量因为疏忽导致该变量流失到全局,如未声明变量,却直接对其进行赋值,就会导致该变量在全局创建,如:
function fn() {
arr = new Array(99999999);
}
fn();
此时,就会在全局创建一个非常大的数组arr,因为是全局变量,所以不被回收,就会一直存在于内存。
解决:
不要在未声明之前使用变量,或者开启严格模式,这样就会出现报错提示
function fn() {
'use strict';
arr = new Array(99999999);
}
fn();
二、闭包
因为闭包是在外部通过一些方式访问到函数内部的变量,即使在外部不再用了,但在外部也就产生了对函数内部变量的引用,该变量也就会一直被标记为活跃变量,存在于内存中,
**解决:**当该变量不在使用时手动给它置null
三、移除存在引用的DOM元素
当你移除某个节点时,本应释放该节点所占内存,但代码中依旧存在对该节点的引用,最终会导致该节点的内存无法被释放,如:
<div id="root">
<div class="child">我是子元素</div>
<button>移除</button>
</div>
<script>
let btn = document.querySelector('button')
let child = document.querySelector('.child')
let root = document.querySelector('#root')
btn.addEventListener('click', function() {
root.removeChild(child)
})
</script>
点击按钮之后会移除child节点,但是全局变量child依旧对其有引用,导致该节点的内存一直无法释放。
解决:
<div id="root">
<div class="child">我是子元素</div>
<button>移除</button>
</div>
<script>
let btn = document.querySelector('button')
btn.addEventListener('click', function() {
let child = document.querySelector('.child')
let root = document.querySelector('#root')
root.removeChild(child)
})
</script>
当方法执行后会退出函数上下文,那么里面的局部变量也会child也会被清理掉,所以就不存在对child的引用了
四、控制台打印console.log
因为浏览器一直保存着我们所打印的信息,我们再能在打开控制台的时候看见我们打印的东西,所以它并不会释放内存
五、定时器
1、setTimeout的第一个参数传字符串而不是function会导致内存泄漏
原因:根据手册强调,不要传递字符串字面量,这会导致和eval一样的问题。具体为什么会因为传递字符串而不是函数会导致内存溢出的原因,经过查阅一些资料,发现 setTimeout和setInterval传入字符串时,若该字符串中存在以var声明的变量,那么该变量会在全局作用域下声明,根据JS垃圾回收机制,全局变量不能被回收。做个例子看一下:
var t = setTimeout(() => {
var c = 10;
console.log("c:", c);
}, 0);
var t2 = setTimeout(() => {
console.log("c2:", c);
}, 2000);
结果:先输出c:10,2秒后提示c未定义,也就是说t2中的c是未定义的
换个方式:
var t = setTimeout('var c = 10; console.log("c:", c);', 0);
var t2 = setTimeout(() => {
console.log("c2:", c);
}, 2000);
结果:先输出c:10,2秒后输出c2:10
所以足以见得,传入字符串给setTimeout,它中间var声明的变量会声明在全局作用域中,哪怕setTimeout已经销毁的,它依旧存在,所以会存在内存溢出的风险。
注意:由于输入的是字符串,它需要先调用JS解析器对字符串进行解析,再去执行解析后的JS代码。会更加占用浏览器内存,容易出现浏览器页面崩溃的情况。
2、忘记清除setInterval也会导致内存泄漏
例子:
function fn() {
let largeObj = new Array(100000)
setInterval(() => {
let myObj = largeObj
}, 1000)
}
fn();
执行fn之后如无setInterval,会退出fn的执行上下文,然后清除局部变量largeObj,但是由于setInterval没有清除,其内部一直存在变量myObj对largeObj的引用,导致largeObj不能被释放。
解决: 不要忘记清除setInterval
六、循环引用
当两个或多个对象存在循环引用时,根据JS垃圾回收机制的引用计数方式,它会标记互相循环引用的对象至少被引用了一次,所以不会释放这些内存
解决: 不要出现循环引用
七、未解绑事件监听器就删除了DOM节点
移除节点是,未先移除事件监听器,导致节点删除后,事件监听器依旧存在,此部分内存不会被释放
解决: 移除节点之前清除事件监听器
八,未删除无用的数据
定义了一些无用的数据,却没有删除,有可能导致内存溢出。
解决: 清理这些无用数据
排查内存泄漏方式可参考这位大佬