Set、Map、WeakSet 和 WeakMap
在 JavaScript 中,Set
、Map
、WeakSet
和 WeakMap
是四种重要的集合类型,它们各自有独特的特性和适用场景。理解它们的区别对于编写高效、安全的代码至关重要。
一、Set(集合)
1. 基本特性
- 唯一值:存储唯一的值,重复值会被自动过滤
- 无序性:元素没有特定顺序,不能通过索引访问
- 可迭代:支持
for...of
、forEach
等迭代方法
2. 常用方法
add(value)
:添加值,返回 Set 本身delete(value)
:删除值,返回布尔值表示是否成功has(value)
:检查值是否存在clear()
:清空集合size
:返回元素数量
3. 示例
const set = new Set();
set.add(1);
set.add(2);
set.add(2); // 重复值,会被忽略
console.log(set.size); // 2
console.log(set.has(2)); // true
set.delete(1);
console.log(set.has(1)); // false
// 迭代
for (const value of set) {
console.log(value); // 2
}
4. 应用场景
- 数组去重:
[...new Set([1, 2, 2, 3])]
- 快速查找:
has
方法效率高于Array.includes
- 存储不重复的值集合
二、Map(映射)
1. 基本特性
- 键值对:存储键值对,键和值可以是任意类型
- 有序性:按照插入顺序迭代元素
- 可迭代:支持
for...of
、forEach
等迭代方法
2. 常用方法
set(key, value)
:设置键值对,返回 Map 本身get(key)
:获取键对应的值,不存在时返回undefined
delete(key)
:删除键值对,返回布尔值表示是否成功has(key)
:检查键是否存在clear()
:清空映射size
:返回键值对数量
3. 示例
const map = new Map();
const objKey = { id: 1 };
map.set(objKey, 'value associated with objKey');
map.set('stringKey', 42);
console.log(map.get(objKey)); // 'value associated with objKey'
console.log(map.has('stringKey')); // true
console.log(map.size); // 2
// 迭代
for (const [key, value] of map) {
console.log(key, value);
}
4. 与 Object 的区别
特性 | Map | Object |
---|---|---|
键的类型 | 任意类型 | 字符串或 Symbol |
顺序性 | 保持插入顺序 | 不保证顺序(ES6+ 字符串键按插入顺序) |
大小 | 通过 size 属性获取 |
需要手动计算 |
默认值 | 无默认键 | 原型链上的键可能导致冲突 |
迭代 | 直接支持 | 需要先获取键数组 |
5. 应用场景
- 复杂键的映射:例如使用对象作为键
- 频繁增删键值对的场景
- 需要有序遍历的场景
三、WeakSet(弱集合)
1. 基本特性
- 只能存储对象:存储的对象必须是引用类型
- 弱引用:对对象的引用是弱引用,不阻止对象被垃圾回收
- 不可迭代:不支持
for...of
、size
等属性和方法 - 方法有限:只有
add
、delete
、has
三个方法
2. 示例
const weakSet = new WeakSet();
const obj = {};
weakSet.add(obj);
console.log(weakSet.has(obj)); // true
// 无法遍历 WeakSet
// 以下代码会报错:TypeError: weakSet is not iterable
// for (const item of weakSet) { ... }
3. 弱引用特性
let obj = {};
const weakSet = new WeakSet();
weakSet.add(obj);
console.log(weakSet.has(obj)); // true
// 释放对象引用
obj = null;
// 垃圾回收后,WeakSet 中该对象会被自动移除
4. 应用场景
- 存储 DOM 元素引用,避免内存泄漏
- 跟踪对象是否存在,但不阻止其被回收
- 实现私有状态(通过 WeakSet 存储对象标识)
四、WeakMap(弱映射)
1. 基本特性
- 键只能是对象:值可以是任意类型
- 弱引用:键对对象的引用是弱引用,不阻止对象被垃圾回收
- 不可迭代:不支持
for...of
、size
等属性和方法 - 方法有限:只有
set
、get
、delete
、has
四个方法
2. 示例
const weakMap = new WeakMap();
const objKey = {};
weakMap.set(objKey, 'secret value');
console.log(weakMap.get(objKey)); // 'secret value'
// 无法遍历 WeakMap
// 以下代码会报错:TypeError: weakMap is not iterable
// for (const [key, value] of weakMap) { ... }
3. 弱引用特性
let obj = {};
const weakMap = new WeakMap();
weakMap.set(obj, 'data');
console.log(weakMap.has(obj)); // true
// 释放对象引用
obj = null;
// 垃圾回收后,WeakMap 中该键值对会被自动移除
4. 应用场景
- 存储对象的私有数据(如 React 的 ref 实现)
- 缓存计算结果,避免内存泄漏
- DOM 元素关联元数据(元素被移除时自动清理)
五、四者对比表格
特性 | Set | Map | WeakSet | WeakMap |
---|---|---|---|---|
存储内容 | 值 | 键值对 | 对象 | 键值对(键为对象) |
键的类型 | 任意类型 | 任意类型 | 必须是对象 | 必须是对象 |
重复值处理 | 自动过滤重复值 | 键唯一,重复会覆盖值 | 不支持重复对象 | 键唯一,重复会覆盖值 |
弱引用 | 否 | 否 | 是 | 是 |
可迭代性 | 是 | 是 | 否 | 否 |
大小属性 | size |
size |
无 | 无 |
典型应用场景 | 去重、成员检测 | 复杂键映射、有序存储 | 临时对象跟踪 | 私有数据、DOM 关联数据 |
六、面试高频问题
Set 和数组的区别是什么?
答:Set 存储唯一值,不支持索引访问,插入和查找效率更高;数组允许重复值,支持索引访问和更多操作方法。Map 和 Object 的主要区别有哪些?
答:Map 的键可以是任意类型,保持插入顺序,有明确的size
属性,且默认没有原型链上的键;Object 的键只能是字符串或 Symbol,不保证顺序,需要手动计算大小。WeakSet 和 WeakMap 的弱引用特性有什么用?
答:弱引用不会阻止对象被垃圾回收,适合存储临时关联数据,避免内存泄漏。例如,当 DOM 元素被移除时,WeakMap 中关联的元数据会自动被清理。什么情况下应该使用 WeakMap?
答:适合以下场景:- 存储对象的私有数据
- 缓存计算结果,且不希望阻止对象被回收
- 关联 DOM 元素的元数据
如何实现一个对象的私有属性?
答:可以使用 WeakMap 实现:const privateData = new WeakMap(); class MyClass { constructor() { privateData.set(this, { secret: 'private value' }); } getSecret() { return privateData.get(this).secret; } }
总结
- Set:用于存储唯一值的集合,适合去重和成员检测
- Map:用于键值对映射,支持任意类型的键,保持插入顺序
- WeakSet:存储对象的弱集合,不阻止对象被回收,不可迭代
- WeakMap:以对象为键的弱映射,用于存储私有数据或临时关联
在选择使用哪种集合类型时,需要考虑数据类型、是否需要弱引用、是否需要迭代以及性能要求等因素。合理使用这些集合类型可以使代码更加高效、安全和易维护。