常见内存泄漏
1. 什么是内存泄漏?
内存泄漏 是指程序运行时,不再使用的内存空间没有被及时释放,导致系统可用内存不断减少。
在浏览器中,内存泄漏会导致页面越来越卡,最终崩溃。
2. 常见泄漏原因 (高频面试题)
(1) 全局变量
意外创建的全局变量是内存泄漏最常见的原因。
// ❌ 忘记声明变量
function foo() {
bar = "This is a global variable"; // 这里实际上是 window.bar
}
// ❌ 意外的 this 指向
function foo() {
this.bar = "global"; // 这里的 this 是 window
}
对策:使用 use strict 模式,严格检查变量声明。避免在全局作用域挂载大量数据。
(2) 闭包
最经典的泄漏原因。闭包会引用外部函数的变量,导致这些变量无法被 GC 回收。
var theThing = null;
var replaceThing = function () {
var originalThing = theThing;
// 这里的 unused 函数引用了 originalThing,即使它从未被调用
var unused = function () {
if (originalThing)
console.log("hi");
};
theThing = {
longStr: new Array(1000000).join('*'),
someMethod: function () {
console.log(someMessage);
}
};
};
// 循环调用 replaceThing 会导致 huge memory leak
(注:这是 Meteor 前端框架早期的经典 bug 示例)
对策:在不需闭包时,解绑闭包函数或者将引用变量赋值为 null。
(3) DOM 引用的失效
当我们保存了一个 DOM 节点的引用,后来从文档中把这个 DOM 删除了,但 JS 中的引用还在,这部分内存(整个 DOM 树)就无法回收。
var elements = {
button: document.getElementById('button')
};
function removeButton() {
document.body.removeChild(document.getElementById('button'));
// 虽然移除了 DOM,但 elements.button 依然引用着它,导致内存无法回收
}
对策:移除 DOM 后,记得把对应的 JS 变量置为 null (elements.button = null)。
(4) 定时器
var someResource = getData();
setInterval(function() {
var node = document.getElementById('Node');
if(node) {
node.innerHTML = JSON.stringify(someResource);
}
}, 1000);
如果节点 Node 被移除了,但定时器还在跑,someResource 就永远无法回收。
对策:组件卸载或页面跳转时 clearInterval / clearTimeout。
(5) 事件监听器
组件挂载时添加了 window.addEventListener,但在卸载时忘记 removeEventListener。
对策:务必在 beforeUnmount 或 useEffect 的 cleanup 函数中移除监听器。
3. 排查工具
使用 Chrome DevTools 的 Memory Panel。
- Timeline: 录制一段时间的操作,看内存曲线是否只升不降。
- Heap Snapshot: 拍快照,对比不同时间点的内存快照,查找未释放对象。
- Allocation Profile: 查看 JS 函数分配内存的热点。