JavaScript中的WeakMap是ES6引入的一种新的集合类型,它和Map一样用于存储键值对,但拥有很多独有的特性。WeakMap的键必须是对象类型,并且这些键对对象的引用是弱引用,这一设计让它在某些场景下比Map更合适。

WeakMap的基本定义与创建
WeakMap的构造函数接收一个可迭代对象作为参数,该可迭代对象的每个元素都应该是包含两个对象的数组,第一个元素是键,第二个元素是值。如果不需要初始化数据,也可以直接调用构造函数创建空WeakMap。
// 创建空WeakMap
const weakMap1 = new WeakMap();
// 创建带初始数据的WeakMap,键必须是对象
const key1 = { id: 1 };
const key2 = { id: 2 };
const weakMap2 = new WeakMap([
[key1, 'value1'],
[key2, 'value2']
]);
console.log(weakMap2.has(key1)); // 输出 true
WeakMap的核心特性
1. 键只能是对象
WeakMap的键不允许是原始类型,比如字符串、数字、布尔值等都不能作为WeakMap的键,尝试使用原始类型作为键会直接抛出类型错误。
const weakMap = new WeakMap();
// 以下代码会抛出TypeError
weakMap.set('stringKey', 'value');
weakMap.set(123, 'value');
2. 弱引用特性
WeakMap对键的引用是弱引用,这意味着如果没有其他变量引用这个键对象,那么即使该对象还在WeakMap中作为键存在,也会被垃圾回收机制回收。而Map对键的引用是强引用,只要键值对还在Map中,键对象就不会被回收。
let obj = { name: 'test' };
const weakMap = new WeakMap();
const map = new Map();
weakMap.set(obj, 'weakValue');
map.set(obj, 'mapValue');
// 移除obj的外部引用
obj = null;
// 此时obj对象没有外部强引用,WeakMap中的键引用是弱引用,所以obj会被垃圾回收
// 而Map中的键是强引用,obj不会被回收,可能导致内存泄漏
3. 不可枚举
WeakMap没有size属性,也不支持迭代方法,比如keys()、values()、entries()都不能使用,也无法用for...of遍历,这是因为弱引用的特性导致无法确定WeakMap中当前的键值对数量,枚举操作也不安全。
const weakMap = new WeakMap();
const key = {};
weakMap.set(key, 'value');
console.log(weakMap.size); // 输出 undefined,WeakMap没有size属性
// 以下代码会抛出TypeError,WeakMap不可枚举
for (const item of weakMap) {}
WeakMap与Map的差异对比
为了更清晰地理解WeakMap的特性,我们可以将它和普通Map做对比,具体差异如下:
| 对比项 | Map | WeakMap |
|---|---|---|
| 键的类型 | 可以是任意类型,包括原始类型和对象 | 只能是对象类型 |
| 引用类型 | 强引用 | 弱引用 |
| 是否可枚举 | 是,支持size属性和迭代方法 | 否,没有size属性,不支持迭代 |
| 内存管理 | 键值对存在时,键不会被垃圾回收 | 键无外部强引用时会被垃圾回收 |
WeakMap的特殊用途
1. 私有数据存储
在ES2022之前,JavaScript没有原生的私有属性语法,我们可以用WeakMap来存储对象的私有数据,因为WeakMap不可枚举,外部无法直接获取存储的内容,并且当对象被回收时,对应的私有数据也会被自动清理。
// 用WeakMap存储私有数据
const privateData = new WeakMap();
class User {
constructor(name, age) {
this.name = name; // 公共属性
// 存储私有属性age
privateData.set(this, { age: age });
}
getAge() {
// 通过WeakMap获取私有属性
return privateData.get(this).age;
}
}
const user = new User('张三', 20);
console.log(user.name); // 输出 张三,公共属性可访问
console.log(user.getAge()); // 输出 20,通过方法获取私有属性
// 无法直接访问privateData中的内容,也无法枚举privateData获取所有私有数据
2. 避免内存泄漏的临时数据存储
当我们需要给对象关联一些临时数据,并且不希望这些关联数据阻止对象被垃圾回收时,WeakMap是很好的选择。比如给DOM元素关联一些状态数据,当DOM元素被移除时,对应的状态数据会自动被回收。
// 给DOM元素关联临时状态
const elementState = new WeakMap();
const button = document.querySelector('button');
// 给按钮关联点击次数状态
elementState.set(button, { clickCount: 0 });
button.addEventListener('click', () => {
const state = elementState.get(button);
state.clickCount++;
console.log(`按钮点击次数:${state.clickCount}`);
});
// 当button元素被从DOM中移除,且没有其它变量引用时,button会被垃圾回收
// 对应的elementState中的键值对也会被自动清理,不会造成内存泄漏
3. 关联对象与元数据
在一些框架或库的开发中,经常需要给对象关联一些元数据,比如给组件实例关联配置信息、给函数关联缓存数据等,使用WeakMap可以避免手动管理这些元数据的清理工作。
// 给函数关联缓存数据
const funcCache = new WeakMap();
function expensiveFunc(obj) {
if (funcCache.has(obj)) {
// 如果已经有缓存,直接返回
return funcCache.get(obj);
}
// 模拟耗时计算
const result = Object.keys(obj).length * 2;
// 存储缓存,键是obj对象
funcCache.set(obj, result);
return result;
}
const dataObj = { a: 1, b: 2 };
console.log(expensiveFunc(dataObj)); // 输出 4
console.log(expensiveFunc(dataObj)); // 输出 4,使用缓存结果
// 当dataObj没有外部引用时,缓存数据会自动被清理
WeakMap的常用方法
WeakMap只提供了四个实例方法,功能比较简单:
- set(key, value):向WeakMap中添加键值对,键必须是对象,返回WeakMap本身,支持链式调用。
- get(key):获取指定键对应的值,如果键不存在则返回undefined。
- has(key):判断WeakMap中是否存在指定的键,返回布尔值。
- delete(key):删除指定的键值对,成功删除返回true,否则返回false。
const weakMap = new WeakMap();
const key = {};
// set方法链式调用
weakMap.set(key, 'value1').set({ id: 2 }, 'value2');
console.log(weakMap.get(key)); // 输出 value1
console.log(weakMap.has(key)); // 输出 true
console.log(weakMap.delete(key)); // 输出 true
console.log(weakMap.has(key)); // 输出 false
使用WeakMap的注意事项
虽然WeakMap有很多优势,但使用时也有一些需要注意的点:
首先,不要试图去获取WeakMap中的所有键或者所有值,因为它本身不支持枚举操作,这也是弱引用特性决定的,枚举操作可能会获取到已经被垃圾回收一半的状态,导致不可预期的问题。
其次,如果需要存储原始类型作为键,或者需要遍历集合中的所有元素,那么应该选择普通的Map而不是WeakMap。WeakMap的设计初衷就是为了解决弱引用相关的场景,不适合所有键值对存储的需求。
最后,在Node.js环境中调试时,WeakMap的内容无法直接通过控制台查看,这也是因为它不可枚举的特性,需要结合具体的键对象才能获取对应的值。
JavaScriptWeakMap弱引用垃圾回收引用类型修改时间:2026-06-30 10:48:46