JavaScript Set 介绍:全面指南
JavaScript 中的 Set 对象是一种非常有用的数据结构,它允许你存储任何类型(无论是原始值还是对象引用)的唯一值。它的设计初衷是为了解决数组中常见的重复元素问题,并提供高效的成员检测。本文将深入探讨 Set 的核心概念、主要特性、常用方法以及在实际开发中的应用场景。
1. Set 的核心概念
Set 对象是值的集合,你可以将其视为一个特殊的数组,但其中不包含任何重复的元素。这意味着如果你尝试向一个 Set 添加一个已经存在的值,它将不会被添加,且 Set 的大小也不会改变。
主要特点:
- 唯一性:
Set中的每个值都是唯一的。 - 无序性:
Set中的元素没有特定的顺序。你不能通过索引访问Set中的元素。 - 迭代性:
Set对象是可迭代的,这意味着你可以使用for...of循环来遍历它的元素。 - 类型多样性: 可以存储任何类型的值,包括
null、undefined、对象和基本数据类型。
2. 创建 Set 对象
你可以使用 Set 构造函数来创建一个新的 Set 对象。
语法:
javascript
new Set([iterable]);
iterable 是一个可选参数,如果提供,其所有元素都将被添加到新的 Set 中。这通常是一个数组。
示例:
“`javascript
// 创建一个空的 Set
const mySet1 = new Set();
console.log(mySet1); // Set(0) {}
// 从数组创建 Set,重复的值会被自动移除
const mySet2 = new Set([1, 2, 3, 2, 4, 5, 1]);
console.log(mySet2); // Set(5) {1, 2, 3, 4, 5}
// 从字符串创建 Set (字符串是可迭代的)
const mySet3 = new Set(“hello”);
console.log(mySet3); // Set(4) {‘h’, ‘e’, ‘l’, ‘o’}
// 存储不同类型的值
const mySet4 = new Set([1, ‘hello’, {a: 1}, null, undefined, 1]);
console.log(mySet4); // Set(5) {1, ‘hello’, {a: 1}, null, undefined}
“`
注意:
* NaN 被视为与 NaN 相同(尽管 NaN === NaN 为 false),因此在 Set 中只能存在一个 NaN。
* 对象引用被认为是不同的,即使它们的内容相同。
javascript
const obj1 = {a: 1};
const obj2 = {a: 1};
const mySet5 = new Set([obj1, obj2]);
console.log(mySet5.size); // 2 (因为 obj1 和 obj2 是不同的对象引用)
3. Set 的常用方法和属性
Set 对象提供了一系列方法来操作其元素。
属性
Set.prototype.size:返回Set对象中元素的数量。
方法
-
Set.prototype.add(value):向Set对象的末尾添加一个新元素。如果该元素已存在,则Set不会改变。返回Set对象本身。javascript
const mySet = new Set();
mySet.add(1); // Set(1) {1}
mySet.add(5); // Set(2) {1, 5}
mySet.add('text'); // Set(3) {1, 5, 'text'}
mySet.add(1); // 1 已存在,Set 不变
console.log(mySet.size); // 3 -
Set.prototype.delete(value):从Set对象中移除指定value的元素。如果Set中存在该元素并被成功移除,返回true;否则返回false。javascript
const mySet = new Set([1, 2, 3]);
console.log(mySet.delete(2)); // true
console.log(mySet); // Set(2) {1, 3}
console.log(mySet.delete(5)); // false (5 不存在) -
Set.prototype.has(value):返回一个布尔值,表示Set对象是否包含指定value的元素。javascript
const mySet = new Set([1, 2, 3]);
console.log(mySet.has(2)); // true
console.log(mySet.has(5)); // false -
Set.prototype.clear():移除Set对象中的所有元素。javascript
const mySet = new Set([1, 2, 3]);
mySet.clear();
console.log(mySet); // Set(0) {}
console.log(mySet.size); // 0 -
Set.prototype.forEach(callbackFn, [thisArg]):对Set对象中的每个元素执行一次提供的callbackFn。callbackFn接收三个参数:value、key(在Set中与value相同) 和set本身。javascript
const mySet = new Set([1, 2, 3]);
mySet.forEach((value, key, set) => {
console.log(`Value: ${value}, Key: ${key}`);
});
// Output:
// Value: 1, Key: 1
// Value: 2, Key: 2
// Value: 3, Key: 3 -
Set.prototype.values():返回一个Iterator对象,其中包含Set对象中的所有值。 Set.prototype.keys():与values()方法相同,因为Set没有键的概念。-
Set.prototype.entries():返回一个Iterator对象,其中包含Set对象中每个元素的[value, value]数组。“`javascript
const mySet = new Set([1, 2]);for (const value of mySet.values()) {
console.log(value); // 1, 2
}for (const key of mySet.keys()) {
console.log(key); // 1, 2
}for (const entry of mySet.entries()) {
console.log(entry); // [1, 1], [2, 2]
}
“`
4. Set 的迭代
由于 Set 是可迭代的,你可以使用 for...of 循环直接遍历其元素,或者使用展开运算符 (...) 将 Set 转换为数组。
“`javascript
const mySet = new Set([‘apple’, ‘banana’, ‘orange’]);
// 使用 for…of 循环
for (const item of mySet) {
console.log(item);
}
// Output:
// apple
// banana
// orange
// 将 Set 转换为数组
const myArray = […mySet];
console.log(myArray); // [‘apple’, ‘banana’, ‘orange’]
// 也可以使用 Array.from()
const myArray2 = Array.from(mySet);
console.log(myArray2); // [‘apple’, ‘banana’, ‘orange’]
“`
5. Set 的常见应用场景
Set 因其唯一性和高效的成员检测,在很多场景下都非常有用。
5.1. 数组去重
这是 Set 最常见和最直接的用途。
javascript
const numbers = [1, 2, 3, 2, 4, 5, 1, 3];
const uniqueNumbers = [...new Set(numbers)];
console.log(uniqueNumbers); // [1, 2, 3, 4, 5]
5.2. 判断元素是否存在
相比于数组的 indexOf 或 includes 方法(它们需要遍历整个数组),Set 的 has() 方法提供了更高效的 O(1)(平均时间复杂度)成员检测。
“`javascript
const bigDataSet = new Set(Array.from({length: 100000}, (_, i) => i));
console.time(‘Set.has’);
console.log(bigDataSet.has(50000)); // true
console.timeEnd(‘Set.has’); // 耗时非常短
const bigArray = Array.from({length: 100000}, (_, i) => i);
console.time(‘Array.includes’);
console.log(bigArray.includes(50000)); // true
console.timeEnd(‘Array.includes’); // 耗时相对较长
“`
5.3. 集合操作(交集、并集、差集)
Set 对象可以非常方便地进行数学上的集合操作。
“`javascript
const setA = new Set([1, 2, 3, 4]);
const setB = new Set([3, 4, 5, 6]);
// 并集 (Union): 所有不重复的元素
const union = new Set([…setA, …setB]);
console.log(union); // Set(6) {1, 2, 3, 4, 5, 6}
// 交集 (Intersection): 两个 Set 中都存在的元素
const intersection = new Set([…setA].filter(x => setB.has(x)));
console.log(intersection); // Set(2) {3, 4}
// 差集 (Difference) A – B: 存在于 A 但不存在于 B 的元素
const difference = new Set([…setA].filter(x => !setB.has(x)));
console.log(difference); // Set(2) {1, 2}
// 差集 (Difference) B – A: 存在于 B 但不存在于 A 的元素
const differenceBMinusA = new Set([…setB].filter(x => !setA.has(x)));
console.log(differenceBMinusA); // Set(2) {5, 6}
“`
5.4. 跟踪唯一 ID 或状态
在需要跟踪页面上唯一 ID 或用户访问状态的场景中,Set 也是一个很好的选择。
“`javascript
const visitedProductIds = new Set();
function markProductAsVisited(productId) {
if (!visitedProductIds.has(productId)) {
visitedProductIds.add(productId);
console.log(Product ${productId} marked as visited for the first time.);
} else {
console.log(Product ${productId} was already visited.);
}
}
markProductAsVisited(‘prod-123’); // Product prod-123 marked as visited for the first time.
markProductAsVisited(‘prod-456’); // Product prod-456 marked as visited for the first time.
markProductAsVisited(‘prod-123’); // Product prod-123 was already visited.
“`
6. Set vs WeakSet
除了 Set,JavaScript 还提供了 WeakSet。WeakSet 只能存储对象引用,并且这些引用是“弱”引用。这意味着如果对象没有其他引用指向它,它将可能被垃圾回收机制回收,即使它还在 WeakSet 中。
主要区别:
Set可以存储任何类型的值,WeakSet只能存储对象引用。Set中的引用是强引用,WeakSet中的引用是弱引用。WeakSet不可迭代,也没有size属性。WeakSet只有add,delete,has方法。
WeakSet 主要用于内存管理,例如在不阻止对象被垃圾回收的情况下,跟踪对象的集合。
7. 总结
JavaScript Set 是一个强大而灵活的数据结构,特别适用于需要存储唯一值和进行高效成员检测的场景。无论是简单的数组去重,还是复杂的集合操作,Set 都能提供简洁高效的解决方案。理解并善用 Set 将显著提升你的 JavaScript 编程效率和代码质量。