Js中常见的内存泄漏场景及处理方式
常见的内存泄漏场景
全局变量的意外创建
一个未声明变量的引用会在全局对象中创建一个新的变量。
没有使用var来创建变量
function foo(arg) {
bar = "this is a hidden global variable";
}
//等价于
function foo(arg) {
window.bar = "this is an explicit global variable";
}
对于this的错误使用
function foo() {
this.variable = "potential accidental global";
}
foo();
foo函数再全局作用域中被调用,因此this指向window
解决方式: 在js文件开头添加 ‘use strict’,开启严格模式。(或者一般将使用过后的全局变量设置为 null 或者将它重新赋值,这个会涉及的缓存的问题,需要注意)
闭包引起的内存泄漏
闭包是JavaScript开发的一个关键方面,闭包可以让你从内部函数访问外部函数作用域,简单来说可以认为是可以从一个函数作用域访问另一个函数作用域而非必要在函数作用域中实现作用域链结构。由于闭包会携带包含它的函数的作用域,因此会比其他函数占用更多的内存,过度使用闭包可能会导致内存占用过多,在不再需要的闭包使用结束后需要手动将其清除。
例如
function debounce(wait, funct, ...args){ // 防抖函数
var timer = null;
return () => {
clearTimeout(timer);
timer = setTimeout(() => funct(...args), wait);
}
}
window.onscroll = debounce(300, (a) => console.log(a), 1);
被遗忘的计时器
计时器setInterval必须及时清理,否则可能由于其中引用的变量或者函数都被认为是需要的而不会进行回收,如果内部引用的变量存储了大量数据,可能会引起页面占用内存过高,这样就造成意外的内存泄漏。
解决方式:当不需要interval或者timeout的时候,调用clearInterval或者clearTimeout
DOM之外的引用
有时保存DOM节点
内部数据结构很有用,例如需要快速更新表格的几行内容,把每一行DOM
存成字典或者数组很有意义。此时同样的DOM
元素存在两个引用:一个在DOM树
中,另一个在字典中。将来如果决定删除这些行时,需要把两个引用都清除。此外还要考虑DOM树
内部或子节点的引用问题,假如你的JavaScript代码中保存了表格某一个<td>
的引用,将来决定删除整个表格的时候,直觉认为GC会回收除了已保存的<td>
以外的其它节点,实际情况并非如此,此<td>
是表格的子节点,子元素与父元素是引用关系,由于代码保留了<td>
的引用,导致整个表格仍待在内存中,所以在保存DOM
元素引用的时候,要小心谨慎。
var elements = {
button: document.getElementById("button"),
image: document.getElementById("image"),
text: document.getElementById("text")
};
function doStuff() {
elements.image.src = "https://www.example.com/1.jpg";
elements.button.click();
console.log(elements.text.innerHTML);
// 更多逻辑
}
function removeButton() {
// 按钮是 body 的后代元素
document.body.removeChild(elements.button);
elements.button = null; // 清除对于这个对象的引用
}
事件监听器被遗忘
当事件监听器在组件内挂载相关的事件处理函数,而在组件销毁时不主动将其清除时,其中引用的变量或者函数都被认为是需要的而不会进行回收,如果内部引用的变量存储了大量数据,可能会引起页面占用内存过高,这样就造成意外的内存泄漏。
MAP被遗忘
当使用Map
存储对象时,类似于脱离DOM的引用,如果不将其主动清除引用,其同样会造成内存不自动进行回收,对于键为对象的情况,可以采用WeakMap
,WeakMap
对象同样用来保存键值对,对于键是弱引用的而且必须为一个对象,而值可以是任意的对象或者原始值,且由于是对于对象的弱引用,其不会干扰Js
的垃圾回收。
SET被遗忘
当使用Set
存储对象时,类似于脱离DOM
的引用,如果不将其主动清除引用,其同样会造成内存不自动进行回收,如果需要使用Set
引用对象,可以采用WeakSet
,WeakSet
对象允许存储对象弱引用的唯一值,WeakSet
对象中的值同样不会重复,且只能保存对象的弱引用,且由于是对于对象的弱引用,其不会干扰Js
的垃圾回收。
console的滥用
控制台日志记录对总体内存内置文件的影响,也是个重大的问题,同时也是容易被忽略的。记录错误的对象,可以将大量的数据保留在内存中。传递给console.log的对象是不能被垃圾回收,所以没有去掉console.log可能会存在内存泄漏
被遗忘的监听者模式
当实现了监听者模式并在组件内挂载相关的事件处理函数,而在组件销毁时不主动将其清除时,其中引用的变量或者函数都被认为是需要的而不会进行回收,如果内部引用的变量存储了大量数据,可能会引起页面占用内存过高,这样就造成意外的内存泄漏。
<template>
<div ></div>
</template>
<script>
export default {
created: function() {
global.eventBus.on("test", this.doSomething);
},
beforeDestroy: function(){
global.eventBus.off("test", this.doSomething);
},
methods: {
doSomething: function() {
// do something
},
},
}
</script>
强引用可能会导致内存泄漏
参考
https://www.cnblogs.com/WindrunnerMax/p/13944643.html
https://www.cnblogs.com/endlessmy/p/8688056.html