JavaScript的垃圾回收机制会自动回收不再被引用的对象,但普通的引用方式如果处理不当,很容易导致对象无法被回收,引发内存泄漏。WeakMap和WeakSet作为弱引用集合,在内存优化场景中发挥着重要作用,它们的设计初衷就是解决普通集合带来的引用残留问题。

WeakMap和WeakSet的基本特性
WeakMap的核心特点
WeakMap是一种键值对的集合,它的键必须是对象,且这些键都是弱引用。这意味着如果除了WeakMap之外没有其他地方引用这个键对象,那么垃圾回收机制可以回收这个对象,对应的键值对也会被自动移除。
WeakMap不支持遍历操作,也没有size属性,这是因为它的内容随时可能因为垃圾回收而变化,无法提前确定集合的大小和所有成员。常用的操作方法只有set()、get()、has()、delete()。
// WeakMap基本使用示例
const weakMap = new WeakMap();
const obj = { name: 'test' };
// 设置键值对
weakMap.set(obj, '对应的value');
// 获取值
console.log(weakMap.get(obj)); // 输出 test
// 判断是否存在键
console.log(weakMap.has(obj)); // 输出 true
// 删除键值对
weakMap.delete(obj);
console.log(weakMap.has(obj)); // 输出 false
WeakSet的核心特点
WeakSet是值的集合,它的值必须是对象,同样是弱引用。如果WeakSet中的某个对象没有被其他引用指向,就会被垃圾回收,该值也会从WeakSet中自动移除。和WeakMap类似,WeakSet也不支持遍历,没有size属性,常用方法有add()、has()、delete()。
// WeakSet基本使用示例
const weakSet = new WeakSet();
const obj1 = { id: 1 };
const obj2 = { id: 2 };
// 添加值
weakSet.add(obj1);
weakSet.add(obj2);
// 判断是否存在值
console.log(weakSet.has(obj1)); // 输出 true
// 删除值
weakSet.delete(obj1);
console.log(weakSet.has(obj1)); // 输出 false
WeakMap/WeakSet与普通Map/Set的对比
普通Map和Set的引用是强引用,只要集合存在,里面的对象就不会被垃圾回收,即使外部已经没有其他引用指向这些对象。我们可以通过下面的表格直观看到两者的区别:
| 对比项 | Map/Set | WeakMap/WeakSet |
|---|---|---|
| 引用类型 | 强引用 | 弱引用 |
| 键/值要求 | 任意类型 | 必须是对象 |
| 是否支持遍历 | 支持 | 不支持 |
| 是否有size属性 | 有 | 无 |
| 内存影响 | 可能导致内存泄漏 | 不会阻止垃圾回收 |
弱引用实现内存优化的原理
JavaScript的垃圾回收机制会定期查找不再被引用的对象并回收其占用的内存。普通Map中如果存储了一个对象作为键,即使这个对象在外部已经没有其他引用,只要Map还存在,这个对象就不会被回收,因为Map对它的引用是强引用。
而WeakMap对键的引用是弱引用,不会计入垃圾回收的引用计数。当外部对某个键对象的引用全部消失后,不管WeakMap是否还存在,这个键对象都会被回收,对应的键值对也会从WeakMap中移除,这样就避免了因为集合持有对象引用导致的内存无法释放问题。
我们可以通过一个简单的示例验证这个特性:
// 普通Map的内存残留示例
const normalMap = new Map();
let objA = { data: 'hello' };
normalMap.set(objA, 'map_value');
objA = null; // 外部引用置空
// 此时normalMap仍然持有objA的强引用,objA不会被回收
// WeakMap的内存自动释放示例
const weakMap = new WeakMap();
let objB = { data: 'world' };
weakMap.set(objB, 'weakmap_value');
objB = null; // 外部引用置空
// 此时WeakMap对objB是弱引用,objB可以被垃圾回收,对应的键值对也会被移除
实际开发中的适用场景
WeakMap的适用场景
- 给对象添加临时附加数据:比如给DOM元素添加一些状态信息,当DOM元素被移除时,这些状态数据会自动被回收,不需要手动清理。
- 存储对象的私有属性:避免私有属性被外部直接访问,同时不会阻止对象被回收。
- 缓存计算结果:当缓存的键对象不再被使用时,缓存数据会自动清除,不需要手动管理缓存过期。
// 给DOM元素附加私有数据的示例
const elementData = new WeakMap();
const button = document.querySelector('button');
// 存储按钮的点击次数
elementData.set(button, 0);
button.addEventListener('click', () => {
const count = elementData.get(button) || 0;
elementData.set(button, count + 1);
console.log(`按钮点击次数:${count + 1}`);
});
// 如果button元素被从DOM中移除且没有其他引用,elementData中对应的数据会自动被回收
WeakSet的适用场景
- 检查对象是否被标记过:比如记录已经处理过的对象,避免重复处理,当对象被回收后,标记会自动消失。
- 存储临时对象集合:比如临时存放一些需要跟踪的对象,当这些对象不再被使用时,集合会自动清理,不需要手动删除。
// 标记已处理对象的示例
const processedObjects = new WeakSet();
function processObj(obj) {
if (processedObjects.has(obj)) {
console.log('该对象已经处理过');
return;
}
// 处理对象逻辑
console.log('处理对象', obj);
processedObjects.add(obj);
}
const targetObj = { task: 'test' };
processObj(targetObj); // 输出 处理对象 {task: 'test'}
processObj(targetObj); // 输出 该对象已经处理过
使用注意事项
虽然WeakMap和WeakSet对内存优化很有帮助,但使用时需要注意以下几点:
- 键和值都必须是对象,不能使用原始类型作为WeakMap的键或者WeakSet的值。
- 因为不支持遍历,所以无法获取集合中的所有成员,也不适合需要遍历集合的场景。
- 没有size属性,无法提前知道集合中有多少成员,也不能清空整个集合。
- 弱引用依赖垃圾回收机制,不同环境的垃圾回收时机不同,所以无法精确控制键值对被移除的时间。
在实际开发中,需要根据场景选择合适的集合类型,如果只是临时存储对象关联数据,且不需要遍历和获取大小,优先使用WeakMap或WeakSet可以有效减少内存泄漏的风险,提升应用的性能。
WeakMapWeakSetJavaScript弱引用内存优化修改时间:2026-06-23 05:12:21