JavaScript 内存管理
1. 内存分配
当 JavaScript 执行代码时,会自动分配内存来存储变量、对象、函数等。内存分配主要包括以下几种情况:
变量声明:声明变量时,JavaScript 会为其分配内存。
let x = 10; // 分配内存存储数字 10
对象创建:创建对象时,JavaScript 会为其分配内存。
let obj = { a: 1, b: 2 }; // 分配内存存储对象
数组创建:创建数组时,JavaScript 会为其分配内存。
let arr = [1, 2, 3]; // 分配内存存储数组
函数声明:声明函数时,JavaScript 会为其分配内存。
function add(a, b) {return a + b;
} // 分配内存存储函数
2. 内存使用
在 JavaScript 中,内存使用主要涉及变量的赋值、对象属性的访问和修改等操作。
变量赋值:将值赋给变量时,会在内存中更新该变量的值。
x = 20; // 更新内存中 x 的值
对象属性访问和修改:访问和修改对象属性时,会在内存中查找和更新相应的属性。
obj.a = 3; // 更新内存中 obj.a 的值
3. 垃圾回收
JavaScript 通过垃圾回收机制自动释放不再使用的内存。垃圾回收的主要策略包括标记-清除(Mark and Sweep)、引用计数(Reference Counting)等。
标记-清除(Mark and Sweep)
这是最常用的垃圾回收算法。
标记:从根节点(通常是全局对象和当前执行的函数)开始,递归地遍历所有可达对象,并标记这些对象。
清除:清除所有未被标记的对象,释放它们占用的内存。
引用计数(Reference Counting)
虽然现代 JavaScript 引擎不常用此方法,但了解其原理有助于理解内存管理。
引用计数:每个对象都有一个引用计数器,每当有一个地方引用该对象时,计数器加一;当引用被移除时,计数器减一。
释放内存:当引用计数器为零时,对象被视为垃圾,可以被回收。
4. 内存泄漏
内存泄漏是指程序在申请内存后,未能释放已分配的内存,导致内存使用量不断增加。常见的内存泄漏原因包括:
未解除事件监听器:忘记移除事件监听器,导致对象无法被垃圾回收。
document.addEventListener('click', function handler() {// 处理点击事件
});
// 忘记移除事件监听器
闭包:闭包可能导致外部变量被长时间引用,从而无法被垃圾回收。
function createClosure() {const largeArray = new Array(1000000).fill(0);return function() {console.log(largeArray.length);};
}
const closure = createClosure();
// largeArray 一直被 closure 引用,无法被垃圾回收
周期性引用:两个对象互相引用,形成周期性引用,导致垃圾回收器无法识别这些对象为垃圾。
const obj1 = {};
const obj2 = {};
obj1.ref = obj2;
obj2.ref = obj1;
// obj1 和 obj2 形成周期性引用
5. 优化建议
及时解除事件监听器:在不再需要事件监听器时,及时移除。
function addClickListener() {const handler = () => {// 处理点击事件};document.addEventListener('click', handler);// 在适当的时候移除事件监听器document.removeEventListener('click', handler);
}
避免不必要的闭包:尽量减少闭包的使用,避免长时间引用外部变量。
function createClosure() {const largeArray = new Array(1000000).fill(0);const length = largeArray.length;return function() {console.log(length);};
}
const closure = createClosure();
// 只引用 length,而不是整个 largeArray
使用弱引用:在某些情况下,可以使用 WeakMap 和 WeakSet 来存储对象,这些对象不会阻止垃圾回收。
const weakMap = new WeakMap();
const obj = {};
weakMap.set(obj, 'value');
// obj 可以被垃圾回收