JavaScript中的WeakMap和WeakSet是ES6引入的特殊数据结构,它们的核心特点是持有对象的弱引用,这一特性让它们在内存管理和特定场景下有普通Map、Set无法替代的作用。

WeakMap和WeakSet的核心特性
要理解它们的特殊用途,首先需要明确和普通Map、Set的差异:
- 键的类型限制:WeakMap的键只能是对象,WeakSet的成员也只能是对象,不能是原始值。
- 弱引用机制:它们对存储的对象是弱引用,当对象没有其他强引用时,会被垃圾回收机制自动回收,对应的键值对或成员也会从结构中消失,无需手动删除。
- 不可枚举:没有size属性,也没有clear、keys、values、entries等可遍历的方法,无法获取所有存储的内容。
WeakMap基础使用示例
WeakMap的常用方法只有get、set、has、delete,示例如下:
// 创建WeakMap实例
const weakMap = new WeakMap();
// 定义两个对象作为键
const obj1 = { name: 'test1' };
const obj2 = { name: 'test2' };
// 设置键值对
weakMap.set(obj1, '关联数据1');
weakMap.set(obj2, '关联数据2');
// 获取值
console.log(weakMap.get(obj1)); // 输出:关联数据1
// 检查是否存在键
console.log(weakMap.has(obj2)); // 输出:true
// 删除键值对
weakMap.delete(obj1);
console.log(weakMap.has(obj1)); // 输出:false
WeakSet基础使用示例
WeakSet的常用方法有add、has、delete,示例如下:
// 创建WeakSet实例
const weakSet = new WeakSet();
// 定义两个对象作为成员
const item1 = { id: 1 };
const item2 = { id: 2 };
// 添加成员
weakSet.add(item1);
weakSet.add(item2);
// 检查是否存在成员
console.log(weakSet.has(item1)); // 输出:true
// 删除成员
weakSet.delete(item1);
console.log(weakSet.has(item1)); // 输出:false
WeakMap的特殊用途
1. 存储对象的私有数据
在ES2022引入私有字段之前,WeakMap是实现对象私有数据的常用方案。因为WeakMap的键是对象,外部无法直接遍历获取所有键,所以可以把对象作为键,把私有数据作为值存储,避免私有数据被外部访问。
// 创建一个闭包内的WeakMap存储私有数据
const privateData = new WeakMap();
class User {
constructor(name, age) {
// 把当前实例作为键,存储私有数据
privateData.set(this, {
name: name,
age: age
});
}
getName() {
// 通过实例从WeakMap中获取私有数据
return privateData.get(this).name;
}
getAge() {
return privateData.get(this).age;
}
}
const user = new User('张三', 20);
console.log(user.getName()); // 输出:张三
console.log(user.getAge()); // 输出:20
// 外部无法直接访问privateData中的内容,也无法遍历privateData获取所有存储的实例数据
2. 关联对象和数据,避免内存泄漏
当需要给DOM节点或者其他对象附加额外数据时,如果用普通Map存储,即使DOM节点被移除,只要Map还存在,节点对象就不会被回收,容易造成内存泄漏。而用WeakMap存储,当DOM节点被移除且没有强引用时,会自动被回收,对应的附加数据也会一起被清理。
// 假设这是一个DOM节点
const domNode = document.getElementById('test-node');
// 用WeakMap存储DOM节点的附加数据
const nodeDataMap = new WeakMap();
nodeDataMap.set(domNode, {
clickCount: 0,
lastClickTime: null
});
// 给DOM节点绑定点击事件
domNode.addEventListener('click', () => {
const data = nodeDataMap.get(domNode);
data.clickCount++;
data.lastClickTime = Date.now();
});
// 当domNode被移除,且没有其它强引用时,domNode会被垃圾回收,nodeDataMap中对应的数据也会被自动清理
WeakSet的特殊用途
1. 检查对象是否被引用过
WeakSet可以用来标记已经处理过的对象,避免重复处理。因为WeakSet的成员只能是对象,且不会阻止对象被回收,适合临时标记场景。
// 创建一个WeakSet标记已经处理过的对象
const processedObjects = new WeakSet();
function processObject(obj) {
// 如果已经处理过,直接返回
if (processedObjects.has(obj)) {
console.log('该对象已经处理过');
return;
}
// 处理对象逻辑
console.log('处理对象', obj);
// 标记为已处理
processedObjects.add(obj);
}
const targetObj = { task: '测试任务' };
processObject(targetObj); // 输出:处理对象 {task: '测试任务'}
processObject(targetObj); // 输出:该对象已经处理过
2. 存储临时对象集合
当需要临时存储一组对象,且不需要遍历这些对象,也不需要知道集合的大小时,WeakSet是更好的选择。比如存储当前正在加载的资源对象,当资源加载完成且没有强引用时,自动从集合中移除,不需要手动清理。
// 存储正在加载的资源对象
const loadingResources = new WeakSet();
function startLoad(resourceObj) {
if (loadingResources.has(resourceObj)) {
console.log('资源正在加载中');
return;
}
loadingResources.add(resourceObj);
// 模拟加载完成
setTimeout(() => {
console.log('资源加载完成');
// 加载完成后,如果resourceObj没有其他强引用,会被自动回收,不需要手动从loadingResources中删除
}, 1000);
}
const resource = { url: 'https://ipipp.com/test.js' };
startLoad(resource);
使用注意事项
虽然WeakMap和WeakSet有独特的优势,但也要注意使用限制:
- 不要尝试遍历它们,因为不可枚举,也没有相关遍历方法。
- 如果需要存储原始值作为键或成员,或者需要获取集合大小、清空集合,还是应该使用普通Map和Set。
- 弱引用是自动触发的,无法手动干预垃圾回收的时机,所以不要依赖它们来精确控制对象的生命周期。
弱引用机制的核心是让垃圾回收器可以正常回收无用的对象,所以在需要临时关联对象数据、且不需要长期持有这些对象引用的场景下,优先选择WeakMap和WeakSet可以有效减少内存泄漏的风险。
WeakMapWeakSetJavaScript弱引用修改时间:2026-07-15 14:51:34