如何避免JavaScript中的内存泄漏?( 二 )


console.log('Hello');
};
};
constsayHello = outer; // contAIns definition of the function inner
functionrepeat(fn, num) {
for(leti = 0; i < num; i++){
fn;
}
}
repeat(sayHello, 10); // each sayHello call pushes another 'Hello' to the potentiallyHugeArray
// now imagine repeat(sayHello, 100000)
在这个例子中,potentiallyHugeArray 从未被任何函数返回,也无法被访问,但它的大小会随着调用 inner 方法的次数而增长 。
3. 定时器
在 Java 中,使用使用 setTimeout 或 setInterval 函数引用对象是防止对象被垃圾回收的最常见方法 。当在代码中设置循环定时器(可以使 setTimeout 表现得像 setInterval,即使其递归)时,只要回调可调用,定时器回调对象的引用就会永远保持活动状态 。
例如下面的这段代码,只有在移除定时器后,data 对象才会被垃圾回收 。在没有移除 setInterval 之前 , 它永远不会被删除,并且 data.hugeString 会一直保留在内存中,直到应用程序停止 。
functionsetCallback{
constdata = https://www.isolves.com/it/cxkf/yy/js/2023-10-27/{
counter: 0,
hugeString: newArray(100000).join('x')
};
returnfunctioncb{
data.counter++; // data object is now part of the callback's scope
console.log(data.counter);
}
}
setInterval(setCallback, 1000); // how do we stop it?
那么应该如何避免上述这种情况的发生呢?可以从以下两个方法入手:

  1. 注意定时器回调引用的对象 。
  2. 必要时取消定时器 。
如下方的代码所示:
functionsetCallback{
// 'unpacking' the data object
letcounter = 0;
consthugeString = newArray(100000).join('x'); // gets removed when the setCallback returns
returnfunctioncb{
counter++; // only counter is part of the callback's scope
console.log(counter);
}
}
consttimerId = setInterval(setCallback, 1000); // saving the interval ID
// doing something ...
clearInterval(timerId); // stopping the timer i.e. if button pressed
4. 事件监听
活动的事件监听器会阻止其范围内的所有变量被回收 。一旦添加,事件监听器会一直生效 , 直到下面两种情况的发生:
  1. 通过 removeEventListener 移除 。
  2. 相关联的 DOM 元素被移除 。
在下面的示例中,使用匿名内联函数作为事件监听器,这意味着它不能与 removeEventListener 一起使用 。此外,由于 document 不能被移除 , 触发方法中的内容会一直驻留内存,即使只使用它触发一次 。
consthugeString = newArray(100000).join('x');
document.addEventListener('keyup', function{ // anonymous inline function - can't remove it
doSomething(hugeString); // hugeString is now forever kept in the callback's scope
});
那么如何避免这种情况呢?可以通过 removeEventListener 释放监听器:
functionlistener{
doSomething(hugeString);
}
document.addEventListener('keyup', listener); // named function can be referenced here...
document.removeEventListener('keyup', listener); // ...and here
如果事件监听器只需要运行一次,addEventListener 可以带有第三个参数,一个提供附加选项的对象 。只要将 {once: true} 作为第三个参数传递给 addEventListener  , 监听器将在事件处理一次后自动删除 。
document.addEventListener('keyup', functionlistener{
doSomething(hugeString);
}, {once: true}); // listener will be removed after running once
5. 缓存
如果不断向缓存中添加内容,而未使用的对象也没有移除,也没有限制缓存的大小,那么缓存的大小就会无限增长:
letuser_1 = { name: "Peter", id: 12345};
letuser_2 = { name: "Mark", id: 54321};
constmapCache = newMap;
functioncache(obj){
if(!mapCache.has(obj)){
constvalue =https://www.isolves.com/it/cxkf/yy/js/2023-10-27/ `${obj.name}has an id of ${obj.id}`;
mapCache.set(obj, value);
return[value, 'computed'];
}
return[mapCache.get(obj), 'cached'];
}
cache(user_1); // ['Peter has an id of 12345', 'computed']
cache(user_1); // ['Peter has an id of 12345', 'cached']
cache(user_2); // ['Mark has an id of 54321', 'computed']
console.log(mapCache); // ((…) => "Peter has an id of 12345", (…) => "Mark has an id of 54321")


推荐阅读