JavaScript 提供了四种主要的集合类型:Set、Map、WeakSet 和 WeakMap。这些集合类型在不同的场景中具有各自独特的优势和使用方式。本文将详细介绍这四种集合类型,通过代码示例展示其使用方法和特点。
Set:独一无二的集合
Set 的创建和基本操作
Set 是一个集合,它的所有元素都是唯一的。我们可以使用 Set 来存储不重复的值。以下是一些基本操作:
// 创建一个新的 Setconst mySet = new Set();// 添加元素mySet.add(1);mySet.add(2);mySet.add(2); // 重复的值不会被添加console.log(mySet); // 输出: Set { 1, 2 }// 检查是否存在某个元素console.log(mySet.has(1)); // 输出: trueconsole.log(mySet.has(3)); // 输出: false// 删除元素mySet.delete(1);console.log(mySet); // 输出: Set { 2 }// 清空 SetmySet.clear();console.log(mySet); // 输出: Set {}
Set 的遍历
可以使用 for...of 循环或 forEach 方法遍历 Set 中的元素。
const set = new Set([1, 2, 3]);// 使用 for...of 循环for (let item of set) {console.log(item); // 输出: 1 2 3}// 使用 forEach 方法set.forEach(item => {console.log(item); // 输出: 1 2 3});
Set 的常见应用场景
- 数组去重:利用
Set的唯一性特性,可以很方便地对数组进行去重。
const numbers = [1, 2, 2, 3, 4, 4, 5];const uniqueNumbers = [...new Set(numbers)];console.log(uniqueNumbers); // 输出: [1, 2, 3, 4, 5]
- 交集、并集和差集:
const setA = new Set([1, 2, 3]);const setB = new Set([3, 4, 5]);// 交集const intersection = new Set([...setA].filter(x => setB.has(x)));console.log(intersection); // 输出: Set { 3 }// 并集const union = new Set([...setA, ...setB]);console.log(union); // 输出: Set { 1, 2, 3, 4, 5 }// 差集const difference = new Set([...setA].filter(x => !setB.has(x)));console.log(difference); // 输出: Set { 1, 2 }
Map:键值对的集合
Map 的创建和基本操作
Map 是一个键值对的集合,其中键和值都可以是任意类型。以下是一些基本操作:
// 创建一个新的 Mapconst myMap = new Map();// 添加键值对myMap.set('name', 'Alice');myMap.set('age', 25);console.log(myMap); // 输出: Map { 'name' => 'Alice', 'age' => 25 }// 获取值console.log(myMap.get('name')); // 输出: Aliceconsole.log(myMap.get('age')); // 输出: 25// 检查是否存在某个键console.log(myMap.has('name')); // 输出: trueconsole.log(myMap.has('address')); // 输出: false// 删除键值对myMap.delete('age');console.log(myMap); // 输出: Map { 'name' => 'Alice' }// 清空 MapmyMap.clear();console.log(myMap); // 输出: Map {}
Map 的遍历
可以使用 for...of 循环遍历 Map 的键值对,或使用 forEach 方法。
const map = new Map([['name', 'Bob'],['age', 30]]);// 使用 for...of 循环for (let [key, value] of map) {console.log(`${key}: ${value}`); // 输出: name: Bob age: 30}// 使用 forEach 方法map.forEach((value, key) => {console.log(`${key}: ${value}`); // 输出: name: Bob age: 30});
Map 的常见应用场景
- 对象键的扩展:
Map允许使用任何类型的键,包括对象,而普通对象的键只能是字符串或符号。
const objKey = { id: 1 };const myMap = new Map();myMap.set(objKey, 'Object Value');console.log(myMap.get(objKey)); // 输出: Object Value
- 计数器:统计元素出现的次数。
const items = ['apple', 'banana', 'apple', 'orange', 'banana', 'apple'];const countMap = new Map();items.forEach(item => {countMap.set(item, (countMap.get(item) || 0) + 1);});console.log(countMap); // 输出: Map { 'apple' => 3, 'banana' => 2, 'orange' => 1 }
WeakSet:弱引用的集合
WeakSet 的创建和基本操作
WeakSet 是一个集合,它的所有元素必须是对象,且对其成员是弱引用。弱引用意味着如果没有其他引用指向某个对象,该对象会被垃圾回收机制回收。
// 创建一个新的 WeakSetconst weakSet = new WeakSet();let obj1 = { name: 'John' };let obj2 = { name: 'Jane' };// 添加对象weakSet.add(obj1);weakSet.add(obj2);console.log(weakSet.has(obj1)); // 输出: true// 删除对象weakSet.delete(obj1);console.log(weakSet.has(obj1)); // 输出: false// 对象被垃圾回收obj2 = null;console.log(weakSet.has(obj2)); // 输出: false
WeakSet 的特点
- 只能包含对象:
WeakSet的元素必须是对象,不能是原始值。 - 弱引用:
WeakSet对其元素是弱引用,不会阻止垃圾回收。
WeakSet 的常见应用场景
- 存储临时对象:
WeakSet适合存储仅在短期内需要使用的对象,避免内存泄漏。
const visitedNodes = new WeakSet();function markNode(node) {visitedNodes.add(node);console.log('Node visited');}const node1 = { id: 1 };markNode(node1);console.log(visitedNodes.has(node1)); // 输出: truenode1 = null; // node1 会被垃圾回收
WeakMap:弱引用的键值对集合
WeakMap 的创建和基本操作
WeakMap 是一个键值对的集合,其中键必须是对象,且对键是弱引用。
// 创建一个新的 WeakMapconst weakMap = new WeakMap();let obj1 = { name: 'Alice' };let obj2 = { name: 'Bob' };// 添加键值对weakMap.set(obj1, 'Engineer');weakMap.set(obj2, 'Designer');console.log(weakMap.get(obj1)); // 输出: Engineer// 删除键值对weakMap.delete(obj1);console.log(weakMap.has(obj1)); // 输出: false// 对象被垃圾回收obj2 = null;console.log(weakMap.has(obj2)); // 输出: false
WeakMap 的特点
- 键必须是对象:
WeakMap的键必须是对象,不能是原始值。 - 弱引用:
WeakMap对其键是弱引用,不会阻止垃圾回收。
WeakMap 的常见应用场景
- 存储关联数据:
WeakMap适合存储与对象关联的数据,避免内存泄漏。
const metaData = new WeakMap();function setMetaData(obj, data) {metaData.set(obj, data);}function getMetaData(obj) {return metaData.get(obj);}const user = { name: 'John' };setMetaData(user, { age: 30 });console.log(getMetaData(user)); // 输出: { age: 30 }user = null; // user 对象会被垃圾回收,metaData 也会自动清除关联数据
集合的比较
| 特性 | Set | Map | WeakSet | WeakMap |
|---|---|---|---|---|
| 存储的内容 | 唯一的值 | 键值对 | 对象 | 键值对 |
| 键的类型 | 值本身即为键 | 任意类型 | 对象 | 对象 |
| 值的类型 | 任意类型 | 任意类型 | 无 | 任意类型 |
| 检查元素存在性 | has 方法 |
has 方法 |
has 方法 |
has 方法 |
| 添加元素或键值对 | add 方法 |
set 方法 |
add 方法 |
set 方法 |
| 删除元素或键值对 | delete 方法 |
delete 方法 |
delete 方法 |
delete 方法 |
| 获取元素或键值对 | 无 | get 方法 |
无 | get 方法 |
| 遍历方法 | forEach、for...of |
forEach、for...of |
无 | 无 |
| 弱引用 | 否 | 否 | 是 | 是 |
深度总结
JavaScript 中的 Set、Map、WeakSet 和 WeakMap 为开发者提供了丰富的集合类型,适用于不同的场景。Set 和 Map 提供了强引用的集合,适合存储需要频繁访问和操作的数据。WeakSet 和 WeakMap 提供了弱引用的集合,适合存储临时数据和避免内存泄漏。
通过深入理解和熟练使用这些集合类型,你可以编写更加高效和可维护的代码,并更好地处理复杂的数据结构和算法。在实际开发中,根据具体需求选择合适的集合类型,能够极大地提升代码的性能和可维护性。因此,掌握这些集合类型是每个 JavaScript 开发者的必备技能。
