4种常见的JavaScript内存泄漏以及如何避免它们
作者:admin 时间:2022-6-21 23:55:8 浏览:在 JavaScript 的上下文中,不需要的引用是保存在代码中某个位置的变量,这些变量将不再使用,并且指向一块本来可以被释放的内存。这就产生了所谓的JavaScript内存泄露。
在本文中,我们将深入了解一下我们的应用程序中可能发生哪些常见的内存泄漏。主要从四种常见的 JavaScript 泄漏进行分析。
要了解哪些是 JavaScript 中最常见的泄漏,我们需要知道引用通常以哪些方式被遗忘。
1、未声明/全局变量
JavaScript 允许的方式之一是它处理未声明变量的方式:对未声明变量的引用会在全局对象内创建一个新变量。
在浏览器的情况下,全局对象是一个“window”。例如:
function foo(arg){
bar = "这是一个隐藏的全局变量";
}
但是,其实是
function foo(arg){
window.bar = "这是一个显式全局变量";
}
要始终避免使用全局变量或防止发生这些错误,请在 JavaScript 文件的开头添加'use strict';
。这启用了一种更严格的 JavaScript 解析模式,可防止意外的全局变量。
2、忘记定时器或回调
在回调中使用 setTimeout
或 setInterval
引用某个对象是防止对象被垃圾回收的最常用方法。如果我们在代码中设置循环计时器,只要回调是可调用的,来自计时器回调的对象的引用就会保持活动状态。
在下面的示例中,data
只有在清除计时器后才能对对象进行垃圾收集。由于我们没有引用setInterval
,因此它永远不会被清除并data.hugeString
保存在内存中,直到应用程序停止,尽管从未使用过。
function setCallback() {
const data = {
counter: 0,
hugeString: new Array(100000).join('x')
};
return function cb() {
data.counter++; // data对象现在是回调范围的一部分
console.log(data.counter);
}
}
setInterval(setCallback(), 1000); // 我们怎样停止它?
如何停止它
如何停止它?特别是如果回调的生命周期未定义或不确定:
- 意识到从计时器的回调中引用的对象,
- 必要时使用从计时器返回的句柄取消它。
- 从计时器返回以在必要时取消它。
function setCallback() {
// “解包”数据对象
let counter = 0;
const hugeString = new Array(100000).join('x'); // 当 setCallback 返回时被移除
return function cb() {
counter++; // 只有计数器是回调范围的一部分
console.log(counter);
}
}
const timerId = setInterval(setCallback(), 1000); // 保存 interval ID
// doing something ...
clearInterval(timerId); // 停止计时器,比如如果按下按钮
3、没有 DOM 引用 (活动事件侦听器)
活动事件侦听器将防止在其范围内捕获的所有变量被垃圾收集。添加后,事件侦听器将一直有效,直到:
- 明确删除removeEventListener()
- 关联的 DOM 元素被移除。
DOM 元素属于 DOM,但它也存在于 Object Graph Memory 中。因此,如果删除前者,则应删除后者。
var trigger = document.getElementById("trigger");
var elem = document.getElementById("elementToDelete");
trigger.addEventListener("click", function(){
elem.remove();
});
在此示例中,单击trigger
后,elementToDelete
将从 DOM 中删除。但是由于它仍然在侦听器中被引用,所以仍然使用为对象分配的内存。
如何防止它
一旦不再需要,我们应该始终取消注册事件侦听器,通过创建指向它的引用并将其传递给removeEventListener()
或 addEventListener()
可以接受第三个参数,这是一个提供附加选项的对象。鉴于它{once: true}
作为第三个参数传递给addEventListener()
,侦听器函数将在处理一次事件后自动删除。
trigger.addEventListener("click", function(){
elem.remove();
},{once: true})); // 监听器运行一次后将被移除
4、闭包
函数范围的变量将在函数退出调用堆栈后清理,如果函数外部没有任何指向它们的引用。尽管函数已经完成执行并且其执行上下文和变量环境早已不复存在,但闭包将保持变量被引用并保持活动状态。
function outer() {
const potentiallyHugeArray = [];
return function inner() {
potentiallyHugeArray.push('Hello');
console.log('Hello');
};
};
const sayHello = outer(); // 包含函数内部的定义
function repeat(fn, num) {
for (let i = 0; i < num; i++){
fn();
}
}
repeat(sayHello, 10); // 每个 sayHello 调用都会将另一个 'Hello' 推送到potentiallyHugeArray
// 现在想象 repeat(sayHello, 100000)
在这个例子中,potentiallyHugeArray
永远不会从任何函数返回,也无法到达,但它的大小可以无限增长,具体取决于我们调用function inner()
。
如何防止它
闭包是 JavaScript 中不可避免的一个组成部分,所以重要的是:
- 了解闭包何时创建以及它保留了哪些对象,
- 了解闭包的预期寿命和使用情况(尤其是用作回调时)。
结论
内存管理过程的组成部分是了解典型的内存泄漏源,以防止它们发生。内存泄漏可能并且确实发生在垃圾收集语言(如 JavaScript)中。这些可能会在一段时间内被忽视,最终它们会造成严重破坏。因此,内存分析工具对于查找内存泄漏至关重要。分析运行应该是开发周期的一部分,尤其是对于中型或大型应用程序。开始这样做是为了给你的用户最好的体验。
相关文章
标签: 内存泄漏
- 站长推荐