JavaScript 内存管理与优化
JavaScript 使用自动垃圾回收机制,但开发者仍需理解内存管理原理以避免常见问题。
内存生命周期
三个阶段
- 分配:变量、对象、函数创建时分配内存
- 使用:读写内存中的值
- 释放:不再使用的内存被回收
内存分配示例
JavaScript
// 基本类型 - 栈内存
let num = 42;
let str = 'hello';
// 引用类型 - 堆内存
let obj = { name: 'JS' };
let arr = [1, 2, 3];
常见内存泄漏
1. 全局变量
JavaScript
// 意外创建全局变量
function leak() {
bar = 'leaked'; // 未声明,挂载到 window
}
// 正确做法
function noLeak() {
'use strict';
let bar = 'safe';
}
2. 闭包引用
JavaScript
// 闭包持有大对象引用
function createClosure() {
const largeData = new Array(1000000);
return function() {
console.log(largeData.length); // 持续引用 largeData
};
}
// 优化:只保留需要的数据
function optimizedClosure() {
const largeData = new Array(1000000);
const length = largeData.length; // 只保存需要的值
return function() {
console.log(length);
};
}
3. 定时器未清理
JavaScript
// 泄漏:定时器持有外部引用
class Component {
constructor() {
this.data = new Array(1000000);
this.timer = setInterval(() => {
console.log(this.data.length);
}, 1000);
}
// 必须清理
destroy() {
clearInterval(this.timer);
this.data = null;
}
}
4. DOM 引用
JavaScript
// 泄漏:DOM 元素被多处引用
const elements = [];
function addElement() {
const el = document.createElement('div');
elements.push(el); // 数组持有引用
document.body.appendChild(el);
}
function removeElement() {
const el = elements.pop();
document.body.removeChild(el);
// el 仍在 elements 数组中被引用!
}
// 正确做法
elements.pop(); // 移除引用
5. 事件监听器
JavaScript
// 泄漏:未移除事件监听
class View {
constructor() {
this.handleClick = this.handleClick.bind(this);
document.addEventListener('click', this.handleClick);
}
handleClick() { /* ... */ }
// 必须移除
destroy() {
document.removeEventListener('click', this.handleClick);
}
}
内存优化技巧
1. 对象池模式
JavaScript
class ObjectPool {
constructor(createFn, resetFn) {
this.pool = [];
this.createFn = createFn;
this.resetFn = resetFn;
}
acquire() {
return this.pool.length > 0
? this.pool.pop()
: this.createFn();
}
release(obj) {
this.resetFn(obj);
this.pool.push(obj);
}
}
// 使用示例
const vectorPool = new ObjectPool(
() => ({ x: 0, y: 0 }),
(v) => { v.x = 0; v.y = 0; }
);
2. 及时释放引用
JavaScript
// 处理大数组后释放
function processLargeData() {
let data = new Array(1000000).fill(0);
// 处理数据
const result = data.map(x => x * 2);
data = null; // 释放内存
return result;
}
3. 使用 WeakMap/WeakSet
JavaScript
// WeakMap 不阻止垃圾回收
const metadata = new WeakMap();
function attachMeta(obj, meta) {
metadata.set(obj, meta);
}
// 当 obj 被回收,meta 自动释放
内存分析工具
Chrome DevTools
- Memory 面板:堆快照、分配时间线
- Performance 面板:内存使用趋势
代码检测
JavaScript
// 检测内存使用
if (process.memoryUsage) {
const used = process.memoryUsage();
console.log({
heapTotal: `${(used.heapTotal / 1024 / 1024).toFixed(2)} MB`,
heapUsed: `${(used.heapUsed / 1024 / 1024).toFixed(2)} MB`
});
}
使用 Chrome Memory 面板的 Heap Snapshot 功能,可以对比两次快照发现未释放的对象。
要点总结
| 泄漏类型 | 原因 | 解决方案 |
|---|---|---|
| 全局变量 | 未声明变量 | 使用严格模式 |
| 闭包 | 持有大对象引用 | 只保留必要数据 |
| 定时器 | 未清理 | destroy 中 clearInterval |
| DOM 引用 | 多处引用 | 移除时清除引用 |
| 事件监听 | 未移除 | removeEventListener |
- 理解栈内存与堆内存的区别
- 及时清理定时器、事件监听器
- 使用 WeakMap 存储元数据
- 定期使用 DevTools 分析内存
📝 发现内容有误?点击此处直接编辑