Js 垃圾回收 与 内存泄漏
首先需要了解 内存生命周期 基础概念:
1.分配所需内存
2.在内存中进行逻辑读/写
3.垃圾清除,释放/回收内存
即 分配内存 → 使用内存 → 释放内存
其中 使用内存 在所有语言中都一样。
但 分配内存 和 释放内存 的逻辑在如 C++ 中的底层语言中是明确的,但在JS这种高级语言中,大部分是隐藏的,JS在定义一个变量时,就已经定义好了内存,同时也提供了垃圾回收的机制,自动回收不再使用的内存。
垃圾回收:
本质是找到内存中可以释放的变量,进行释放 回收内存。
内存泄漏:
本质是程序对一段内存失去了控制权:程序认为一个变量已经没用了,但垃圾回收引擎认为这个变量还在使用 不能释放,从而导致这段内存不能释放。
注意内存突然暴增不等于内存泄漏,只能说代码性能没有做好,需要优化。
泄漏的本质在于能否控制,而不是突然的增量有多少。
常见的JS内存泄漏情况:
1.意外的全局变量
原因:未声明的变量会被隐式创建为全局变量(window 对象的属性),除非手动释放,否则会一直存在。
function leak() {
// 未使用 var/let/const,变量变为全局
globalVar = 'This is a global variable';
this.anotherGlobal = 'Another global via this';
}
leak(); // 执行后,globalVar 和 anotherGlobal 成为全局变量
解决:使用严格模式("use strict"
),或始终用 let/const
声明变量。
2.未清除的定时器或回调
原因:setInterval
或 setTimeout
的回调中引用了外部变量,定时器未清除时,变量无法释放。
let bigData = new Array(1000000).fill('data');
const timer = setInterval(() => {
console.log(bigData.length); // 定时器持有 bigData 的引用
}, 1000);
// 即使不再需要 bigData,若不调用 clearInterval(timer),内存无法释放
解决:及时用 clearInterval
/clearTimeout
清除定时器。
3. 闭包长期持有外部变量
function createClosure() {
const hugeData = new Array(1000000).fill('data');
return () => console.log(hugeData[0]); // 闭包引用了 hugeData
}
const closure = createClosure();
// closure 长期存在时,hugeData 无法被回收
解决:在不需要时解除对闭包的引用(如 closure = null
)。
4. 未移除的 DOM 事件监听
原因:DOM 元素被移除后,若事件监听器未移除且回调中引用了该元素,元素无法被回收。
function addListener() {
const button = document.getElementById('button');
button.addEventListener('click', () => {
console.log('Button clicked'); // 闭包引用了 button
});
}
addListener();
// 即使从 DOM 中移除 button,事件监听器仍持有其引用
解决:使用 removeEventListener
或在元素移除前解绑事件。
5. 残留的 DOM 引用
原因:JS 中保留了对已移除 DOM 元素的引用,导致元素无法被 GC 回收。
const elements = [];
function addElement() {
const div = document.createElement('div');
elements.push(div); // 将 DOM 元素存入数组
document.body.appendChild(div);
}
// 即使调用 document.body.removeChild(div),elements 数组仍保留引用
解决:手动清理数组(如 elements.length = 0
)。
6. 脱离的 DOM 树引用
原因:父节点从 DOM 移除,但子节点仍被 JS 引用,导致整个子树无法回收。
let parent = document.createElement('div');
let child = document.createElement('p');
parent.appendChild(child);
document.body.appendChild(parent);
// 从 DOM 移除 parent
document.body.removeChild(parent);
// 若其他地方仍引用 child,parent 和 child 均无法释放
解决:解除对子节点的引用(如 child = null
)。
7. 无限增长的缓存
原因:缓存对象未设置清理策略,导致数据持续占用内存。
const cache = {};
function setCache(key, value) {
cache[key] = value; // 不断添加新键值对,无清理机制
}
// 长期运行后,cache 占用内存无限增长
解决:使用 LRU(最近最少使用)算法或设置过期时间清理缓存。