JavaScript 提供了四种主要的集合类型:Set
、Map
、WeakSet
和 WeakMap
。这些集合类型在不同的场景中具有各自独特的优势和使用方式。本文将详细介绍这四种集合类型,通过代码示例展示其使用方法和特点。
Set:独一无二的集合
Set 的创建和基本操作
Set
是一个集合,它的所有元素都是唯一的。我们可以使用 Set
来存储不重复的值。以下是一些基本操作:
// 创建一个新的 Set
const mySet = new Set();
// 添加元素
mySet.add(1);
mySet.add(2);
mySet.add(2); // 重复的值不会被添加
console.log(mySet); // 输出: Set { 1, 2 }
// 检查是否存在某个元素
console.log(mySet.has(1)); // 输出: true
console.log(mySet.has(3)); // 输出: false
// 删除元素
mySet.delete(1);
console.log(mySet); // 输出: Set { 2 }
// 清空 Set
mySet.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
是一个键值对的集合,其中键和值都可以是任意类型。以下是一些基本操作:
// 创建一个新的 Map
const myMap = new Map();
// 添加键值对
myMap.set('name', 'Alice');
myMap.set('age', 25);
console.log(myMap); // 输出: Map { 'name' => 'Alice', 'age' => 25 }
// 获取值
console.log(myMap.get('name')); // 输出: Alice
console.log(myMap.get('age')); // 输出: 25
// 检查是否存在某个键
console.log(myMap.has('name')); // 输出: true
console.log(myMap.has('address')); // 输出: false
// 删除键值对
myMap.delete('age');
console.log(myMap); // 输出: Map { 'name' => 'Alice' }
// 清空 Map
myMap.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
是一个集合,它的所有元素必须是对象,且对其成员是弱引用。弱引用意味着如果没有其他引用指向某个对象,该对象会被垃圾回收机制回收。
// 创建一个新的 WeakSet
const 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)); // 输出: true
node1 = null; // node1 会被垃圾回收
WeakMap:弱引用的键值对集合
WeakMap 的创建和基本操作
WeakMap
是一个键值对的集合,其中键必须是对象,且对键是弱引用。
// 创建一个新的 WeakMap
const 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 开发者的必备技能。