JavaScript 中的 Map、WeakMap、Set 详解
在 JavaScript 中,Map
、WeakMap
、Set
是用于存储数据集合的特殊对象类型,它们各有特点和应用场景。下面我将详细解释它们的用法并提供具体案例。
1. Map(映射)
Map 是一种键值对集合,其中键可以是任意类型(对象、原始值等)。
基本用法
javascript
// 创建 Map
const userMap = new Map();
// 添加键值对
userMap.set('name', 'Alice');
userMap.set(1, 'ID');
userMap.set({ role: 'admin' }, '权限对象');
// 获取值
console.log(userMap.get('name')); // 'Alice'
console.log(userMap.get(1)); // 'ID'
// 检查键是否存在
console.log(userMap.has('name')); // true
// 删除键值对
userMap.delete(1);
// 获取大小
console.log(userMap.size); // 2
// 遍历 Map
userMap.forEach((value, key) => {
console.log(`${key}: ${value}`);
});
// 清空 Map
userMap.clear();
使用对象作为键
javascript
const user1 = { id: 1 };
const user2 = { id: 2 };
const preferences = new Map();
preferences.set(user1, { theme: 'dark', lang: 'en' });
preferences.set(user2, { theme: 'light', lang: 'fr' });
console.log(preferences.get(user1).theme); // 'dark'
与普通对象的区别
特性 | Map | 普通对象 |
---|---|---|
键的类型 | 任意类型 | String/Symbol |
顺序 | 插入顺序 | 无序 |
大小 | .size 属性 | 手动计算 |
性能 | 频繁增删时更优 | 相对较慢 |
默认键 | 无 | 有原型链属性 |
2. WeakMap(弱映射)
WeakMap 与 Map 类似,但有重要区别:
- 键必须是对象
- 键是弱引用(不阻止垃圾回收)
- 不可遍历(没有迭代方法)
基本用法
javascript
const weakMap = new WeakMap();
let obj1 = { id: 1 };
let obj2 = { id: 2 };
// 设置键值对
weakMap.set(obj1, '私有数据1');
weakMap.set(obj2, '私有数据2');
// 获取值
console.log(weakMap.get(obj1)); // '私有数据1'
// 删除
weakMap.delete(obj1);
// 检查存在
console.log(weakMap.has(obj2)); // true
典型应用:私有成员
javascript
const privateData = new WeakMap();
class User {
constructor(name) {
// 每个实例的私有数据存储在WeakMap中
privateData.set(this, { name });
}
getName() {
return privateData.get(this).name;
}
}
const user = new User('Alice');
console.log(user.getName()); // 'Alice'
console.log(privateData.get(user)); // { name: 'Alice' }
console.log(user.name); // undefined(真正私有)
WeakMap 特点
- 当键对象被垃圾回收时,对应的值也会自动清除
- 没有
size
属性 - 不能使用
forEach()
或for...of
遍历 - 不支持清空整个 WeakMap
3. Set(集合)
Set 是唯一值的集合,每个值只能出现一次。
基本用法
javascript
const uniqueNumbers = new Set();
// 添加值
uniqueNumbers.add(1);
uniqueNumbers.add(2);
uniqueNumbers.add(2); // 重复值会被忽略
uniqueNumbers.add('text');
uniqueNumbers.add({ id: 1 });
// 检查存在
console.log(uniqueNumbers.has(1)); // true
// 获取大小
console.log(uniqueNumbers.size); // 4
// 删除值
uniqueNumbers.delete('text');
// 遍历 Set
uniqueNumbers.forEach(value => {
console.log(value);
});
// 转换为数组
const numbersArray = [...uniqueNumbers];
// 清空
uniqueNumbers.clear();
常见应用场景
1. 数组去重
javascript
const duplicates = [1, 2, 2, 3, 4, 4, 5];
const unique = [...new Set(duplicates)];
console.log(unique); // [1, 2, 3, 4, 5]
2. 求交集/并集/差集
javascript
const setA = new Set([1, 2, 3]);
const setB = new Set([2, 3, 4]);
// 并集
const union = new Set([...setA, ...setB]);
// 交集
const intersection = new Set([...setA].filter(x => setB.has(x)));
// 差集 (A - B)
const difference = new Set([...setA].filter(x => !setB.has(x)));
console.log([...union]); // [1, 2, 3, 4]
console.log([...intersection]); // [2, 3]
console.log([...difference]); // [1]
3. 跟踪唯一对象
javascript
const visitedNodes = new Set();
function traverse(node) {
if (visitedNodes.has(node)) return;
visitedNodes.add(node);
// 处理节点...
for (let child of node.children) {
traverse(child);
}
}
对比总结表
特性 | Map | WeakMap | Set |
---|---|---|---|
键类型 | 任意类型 | 必须是对象 | 存储值(无键) |
值类型 | 任意类型 | 任意类型 | 唯一值 |
可迭代 | 是 | 否 | 是 |
大小 | .size 属性 |
不可获取 | .size 属性 |
垃圾回收 | 强引用(阻止回收) | 弱引用(不阻止回收) | 强引用(阻止回收) |
遍历方法 | forEach , for...of , keys |
无 | forEach , for...of |
主要用途 | 键值对存储(键可为对象) | 对象关联元数据/私有成员 | 唯一值集合/数学集合操作 |
性能 | 增删查 O(1) | 增删查 O(1) | 增删查 O(1) |
什么时候使用?
-
使用 Map 当需要:
- 键值对集合且键不是字符串
- 需要保持插入顺序
- 需要频繁添加/删除键值对
-
使用 WeakMap 当需要:
- 关联对象与元数据而不影响垃圾回收
- 实现真正的私有属性
- 存储对象特定的元信息
-
使用 Set 当需要:
- 存储唯一值
- 快速检查值是否存在
- 执行集合操作(并集、交集等)
这些集合类型为JavaScript提供了更强大的数据结构处理能力,根据具体场景选择合适的类型可以写出更高效、更安全的代码。