深入掌握 JavaScript Set
:一个独特的集合类型
在 JavaScript 的世界里,我们处理数据集合的方式多种多样。从传统的数组(Array)和对象(Object),到 ES6 新引入的 Map,每种数据结构都有其特定的用途和优势。今天,我们将聚焦于另一个强大的 ES6 数据结构——Set
(集合)。
Set
提供了一种存储唯一值的机制,这使得它在许多场景下都表现得异常便捷和高效。与数组不同,你在 Set
中添加重复的值时,它们只会被存储一次。这种特性让 Set
成为了处理需要去重或快速判断元素是否存在等问题的理想选择。
本文将带你从入门到精通,全面了解 JavaScript Set
的各个方面:
Set
的基本概念与特性- 创建
Set
的多种方式 Set
的核心操作方法:添加、删除、检查、清空- 理解
Set
中“唯一值”的含义,特别是对于不同数据类型的处理 - 遍历
Set
中的元素 Set
与数组(Array)之间的相互转换Set
的实际应用场景,包括数学集合运算(并集、交集、差集等)Set
与其他数据结构的比较Set
的性能优势分析- 使用
Set
时可能遇到的进阶问题和注意事项
让我们开始这段旅程吧!
1. Set 的基本概念与特性
Set
是 ES6(ECMAScript 2015)引入的一种新的集合类型。它的主要特点如下:
- 存储唯一值:
Set
中的任何两个元素都不会是严格相等(Strict Equality)或逻辑上的重复。当你尝试添加一个已经存在于Set
中的值时,添加操作会被忽略,Set
不会发生改变。 - 无序性:
Set
中的元素没有索引,它们不像数组那样通过下标访问。虽然在某些 JavaScript 引擎实现中,遍历Set
时会按照元素添加的顺序进行,但这并不是规范强制要求的,因此我们不应该依赖这种顺序。将其视为无序集合更为稳妥。 - 迭代器:
Set
是可迭代(Iterable)的。这意味着你可以使用for...of
循环来遍历Set
中的元素,也可以使用扩展运算符 (...
) 将Set
转换为数组。 - 值相等性判断:
Set
使用一种称为“SameValueZero”的算法来判断两个值是否相等。这个算法类似于严格相等 (===
),但有两个特别之处:NaN
被认为与NaN
相等(而NaN === NaN
返回false
)。+0
和-0
被认为是相等的(而+0 === -0
返回true
)。
2. 创建 Set
创建 Set
非常简单,你可以使用 Set
构造函数。
方式一:创建一个空的 Set
javascript
const mySet = new Set();
console.log(mySet); // Set(0) {}
方式二:使用可迭代对象初始化 Set
Set
构造函数可以接受一个可选参数,该参数必须是一个可迭代对象(Iterable),比如数组、字符串、Map 等。Set 会将这个可迭代对象中的所有元素添加到自身中,并自动去除重复项。
“`javascript
// 使用数组初始化
const setFromArray = new Set([1, 2, 3, 3, 4, 5, 5, 6]);
console.log(setFromArray); // Set(6) { 1, 2, 3, 4, 5, 6 } // 重复的 3 和 5 被自动去除了
// 使用字符串初始化 (字符串是可迭代的)
const setFromString = new Set(“hello world”);
console.log(setFromString); // Set(8) { ‘h’, ‘e’, ‘l’, ‘o’, ‘ ‘, ‘w’, ‘r’, ‘d’ } // 注意:’l’ 和 ‘o’ 只出现一次,空格也算一个元素
// 使用 Set 初始化另一个 Set (这通常用于复制 Set,但也会去重)
const anotherSet = new Set(setFromArray);
console.log(anotherSet); // Set(6) { 1, 2, 3, 4, 5, 6 }
“`
使用可迭代对象初始化是创建 Set
并同时填充数据的常用方式,尤其是在需要从现有数据源(如数组)中提取唯一值时。
3. Set 的核心操作方法
Set
对象提供了一系列方法来管理集合中的元素:
set.add(value)
: 添加一个新元素到 Set 中。如果该元素已存在,Set 不会改变。返回 Set 本身,因此可以链式调用。set.delete(value)
: 从 Set 中移除指定的元素。如果元素存在并成功移除,返回true
;如果元素不存在,返回false
。set.has(value)
: 检查 Set 中是否存在指定的元素。返回一个布尔值 (true
或false
)。这是Set
最有用的方法之一,提供了高效的查找能力。set.clear()
: 移除 Set 中的所有元素,使其变为空 Set。set.size
: 返回 Set 中当前元素的数量。
下面是这些方法的使用示例:
“`javascript
const fruits = new Set();
// add() 方法
fruits.add(“apple”);
fruits.add(“banana”);
console.log(fruits); // Set(2) { ‘apple’, ‘banana’ }
// 尝试添加重复元素
fruits.add(“apple”);
console.log(fruits); // Set(2) { ‘apple’, ‘banana’ } // Set 大小和内容都没变
// add() 方法链式调用
fruits.add(“orange”).add(“grape”);
console.log(fruits); // Set(4) { ‘apple’, ‘banana’, ‘orange’, ‘grape’ }
// has() 方法
console.log(fruits.has(“banana”)); // true
console.log(fruits.has(“kiwi”)); // false
// delete() 方法
console.log(fruits.delete(“banana”)); // true (成功删除)
console.log(fruits); // Set(3) { ‘apple’, ‘orange’, ‘grape’ }
console.log(fruits.delete(“kiwi”)); // false (尝试删除不存在的元素)
console.log(fruits); // Set(3) { ‘apple’, ‘orange’, ‘grape’ }
// size 属性
console.log(fruits.size); // 3
// clear() 方法
fruits.clear();
console.log(fruits); // Set(0) {}
console.log(fruits.size); // 0
“`
这些基本操作构成了使用 Set
的基础。理解它们的功能和返回值对于高效地使用 Set
至关重要。
4. 理解 Set 中的“唯一值”
Set
的核心特性是存储唯一值。理解它是如何判断值是否唯一的非常重要。Set
使用 SameValueZero
算法进行值相等性比较。
-
原始类型 (Primitives): 对于字符串、数字、布尔值、
null
、undefined
以及 Symbol,只要值本身相同,就被认为是重复的。1
和1
是相同的。"hello"
和"hello"
是相同的。true
和true
是相同的。null
和null
是相同的。undefined
和undefined
是相同的。- 特殊情况 1: NaN
Set
中NaN
被认为与NaN
相等。这意味着你只能往 Set 中添加一个NaN
。
javascript
const numSet = new Set([1, 5, NaN, 3, NaN]);
console.log(numSet); // Set(4) { 1, 5, NaN, 3 } // 只有一个 NaN
console.log(numSet.has(NaN)); // true - 特殊情况 2: +0 和 -0
Set
中+0
和-0
被认为相等,因此只能添加一个。
javascript
const zeroSet = new Set([0, -0, +0]);
console.log(zeroSet); // Set(1) { 0 } // 只有一个 0
console.log(zeroSet.has(0)); // true
console.log(zeroSet.has(-0)); // true // has 认为它们相等
-
对象类型 (Objects): 对于对象(包括普通对象字面量
{}
、数组[]
、函数function(){}
等),Set
判断唯一性是基于对象的引用,而不是对象的内容。只有当两个变量引用的是同一个对象时,它们才被认为是相同的。
“`javascript
const obj1 = { id: 1 };
const obj2 = { id: 2 };
const obj3 = obj1; // obj3 引用了 obj1const objSet = new Set();
objSet.add(obj1);
objSet.add(obj2);
objSet.add(obj3); // 尝试添加 obj3 (它和 obj1 是同一个引用)console.log(objSet); // Set(2) { { id: 1 }, { id: 2 } } // obj1 和 obj2 被添加,obj3 没有被添加因为它引用了 obj1
console.log(objSet.size); // 2console.log(objSet.has(obj1)); // true
console.log(objSet.has(obj2)); // true
console.log(objSet.has({ id: 1 })); // false!因为这是另一个新的对象引用
``
Set` 中仍然会被视为两个不同的元素,因为它们是不同的对象实例,拥有不同的内存地址引用。
这意味着,如果你创建了两个具有完全相同内容的数组或对象,它们在
理解 Set
如何判断唯一性是避免意外行为的关键。特别是处理对象类型时,要记住它比较的是引用而非内容。
5. 遍历 Set
Set
是可迭代的,因此可以使用多种方式遍历其中的元素:
方式一:使用 for...of
循环
这是遍历 Set 最常用和推荐的方式。它直接迭代 Set 中的值。
“`javascript
const colors = new Set([“red”, “green”, “blue”]);
for (const color of colors) {
console.log(color);
}
// 输出:
// red
// green
// blue
“`
方式二:使用 forEach()
方法
Set
也提供了 forEach()
方法,其签名与 Array.prototype.forEach()
类似,但参数略有不同。forEach
方法接受一个回调函数,该函数会为 Set 中的每个元素执行一次。回调函数接收三个参数:
value
: 当前正在遍历的元素的值。key
: 当前正在遍历的元素的键。在 Set 中,键和值是相同的。set
: 调用forEach()
方法的 Set 对象本身。
“`javascript
const numbers = new Set([10, 20, 30]);
numbers.forEach((value, key, set) => {
console.log(Value: ${value}, Key: ${key}
); // Note: key is the same as value
console.log(“Set object:”, set); // Logs the entire Set object
});
// 输出:
// Value: 10, Key: 10
// Set object: Set(3) { 10, 20, 30 }
// Value: 20, Key: 20
// Set object: Set(3) { 10, 20, 30 }
// Value: 30, Key: 30
// Set object: Set(3) { 10, 20, 30 }
``
forEach
请注意,由于 Set 没有键的概念,的第二个参数 (
key) 和第一个参数 (
value) 是相同的。这是为了保持与
Map.prototype.forEach` 的方法签名一致性。
方式三:使用迭代器方法 (keys()
, values()
, entries()
)
Set
对象有三个方法可以返回迭代器对象:
set.keys()
: 返回一个迭代器,包含 Set 中的所有值(与values()
相同)。set.values()
: 返回一个迭代器,包含 Set 中的所有值(与keys()
相同)。set.entries()
: 返回一个迭代器,包含 Set 中所有[value, value]
格式的数组。同样,由于 Set 没有键,键和值是相同的。
这些迭代器可以使用 for...of
循环遍历,或者与 next()
方法一起使用。
“`javascript
const letters = new Set([‘a’, ‘b’, ‘c’]);
// Using values() (or keys())
const valuesIterator = letters.values();
console.log(valuesIterator.next()); // { value: ‘a’, done: false }
console.log(valuesIterator.next()); // { value: ‘b’, done: false }
console.log(valuesIterator.next()); // { value: ‘c’, done: false }
console.log(valuesIterator.next()); // { value: undefined, done: true }
// Using entries()
const entriesIterator = letters.entries();
for (const entry of entriesIterator) {
console.log(entry); // entry is [value, value]
}
// Output:
// [ ‘a’, ‘a’ ]
// [ ‘b’, ‘b’ ]
// [ ‘c’, ‘c’ ]
``
for…of
在实际开发中,通常使用循环或
forEach()方法来遍历
Set,它们更简洁直观。
values()(或
keys())和
entries()` 方法在需要更底层控制迭代过程时可能会用到。
6. Set 与数组之间的相互转换
在 Set
和数组之间进行转换是常见的操作。
数组转换为 Set
使用 Set
构造函数,它接受任何可迭代对象作为参数,包括数组。这是一种非常简洁的去重方式。
javascript
const numbersArray = [1, 2, 3, 3, 4, 5, 5, 6];
const numbersSet = new Set(numbersArray);
console.log(numbersSet); // Set(6) { 1, 2, 3, 4, 5, 6 }
Set 转换为数组
有几种方法可以将 Set
转换为数组:
- 使用扩展运算符 (
...
): 由于Set
是可迭代的,你可以直接在数组字面量中使用扩展运算符将其元素展开。这是最常用和最简洁的方法。
javascript
const colorsSet = new Set(["red", "green", "blue"]);
const colorsArray = [...colorsSet];
console.log(colorsArray); // [ 'red', 'green', 'blue' ] - 使用
Array.from()
方法:Array.from()
方法可以从一个可迭代对象或类数组对象创建一个新的 Array 实例。
javascript
const fruitsSet = new Set(["apple", "banana", "orange"]);
const fruitsArray = Array.from(fruitsSet);
console.log(fruitsArray); // [ 'apple', 'banana', 'orange' ]
这两种方法都能轻松地将Set
转换为数组,选择哪种取决于个人偏好,但扩展运算符通常被认为更具现代感和简洁性。
7. Set 的实际应用场景
Set
的唯一值特性使其在多种编程场景中非常有用。
7.1 数组去重 (Removing Duplicate Elements from an Array)
这是 Set
最常见和广为人知的用例。结合数组到 Set 再到数组的转换,可以轻松实现数组去重。
“`javascript
const arrayWithDuplicates = [1, 2, 2, 3, 4, 4, 5, ‘a’, ‘b’, ‘b’, ‘a’];
const uniqueArray = […new Set(arrayWithDuplicates)];
// 或者 const uniqueArray = Array.from(new Set(arrayWithDuplicates));
console.log(uniqueArray); // [ 1, 2, 3, 4, 5, ‘a’, ‘b’ ]
``
Set` 构造函数自动去重的特性。
这个方法非常简洁高效,利用了
7.2 快速检查元素是否存在 (Checking for Element Existence Efficiently)
在需要频繁检查一个元素是否存在于一个集合中时,将集合存储在 Set
中通常比存储在数组中更有效率。Set
的 has()
方法提供了平均 O(1) 的时间复杂度进行查找(在大多数情况下),而数组的 includes()
方法通常需要遍历整个数组,时间复杂度是 O(n)。对于大型数据集,这种性能差异会很显著。
“`javascript
const largeArray = Array.from({ length: 100000 }, (_, i) => i);
const largeSet = new Set(largeArray);
console.time(“Array includes”);
largeArray.includes(99999); // Searching for the last element
console.timeEnd(“Array includes”); // Might take a few milliseconds
console.time(“Set has”);
largeSet.has(99999); // Searching for the last element
console.timeEnd(“Set has”); // Should be much faster, often near 0 ms
// Example of practical use: Tracking visited items
const visitedUserIds = new Set();
function userVisitedPage(userId) {
if (visitedUserIds.has(userId)) {
console.log(User ${userId} has visited before.
);
} else {
console.log(User ${userId} is visiting for the first time.
);
visitedUserIds.add(userId);
}
}
userVisitedPage(101); // User 101 is visiting for the first time.
userVisitedPage(105); // User 105 is visiting for the first time.
userVisitedPage(101); // User 101 has visited before.
“`
7.3 实现数学集合运算 (Implementing Mathematical Set Operations)
Set
数据结构非常适合用来实现数学中的集合运算,如并集、交集、差集等。虽然 JavaScript 的 Set
对象本身没有内置这些方法,但我们可以很容易地使用 Set
和其迭代特性来实现它们。
假设我们有两个 Set:setA
和 setB
。
-
并集 (Union): 包含存在于
setA
或setB
中的所有唯一元素。
“`javascript
const setA = new Set([1, 2, 3]);
const setB = new Set([3, 4, 5]);// 方法一:使用扩展运算符
const unionSet = new Set([…setA, …setB]);
console.log(“Union:”, unionSet); // Set(5) { 1, 2, 3, 4, 5 }// 方法二:手动迭代添加
const unionSetManual = new Set(setA); // Start with elements from A
for (const element of setB) {
unionSetManual.add(element); // Add elements from B (add handles duplicates)
}
console.log(“Union (Manual):”, unionSetManual); // Set(5) { 1, 2, 3, 4, 5 }
“`
使用扩展运算符是最简洁的方式。 -
交集 (Intersection): 包含同时存在于
setA
和setB
中的元素。
“`javascript
const setA = new Set([1, 2, 3, 4]);
const setB = new Set([3, 4, 5, 6]);const intersectionSet = new Set();
for (const element of setA) {
if (setB.has(element)) { // Check if element from setA is also in setB
intersectionSet.add(element);
}
}
console.log(“Intersection:”, intersectionSet); // Set(2) { 3, 4 }// 或者使用 Array.filter (先转为数组)
// const intersectionArray = […setA].filter(element => setB.has(element));
// const intersectionSetFromArray = new Set(intersectionArray);
// console.log(“Intersection (from Array):”, intersectionSetFromArray); // Set(2) { 3, 4 }
``
has()` 方法检查是否存在于另一个 Set 中,可以高效地找到交集。
通过迭代其中一个 Set 并使用 -
差集 (Difference) (A – B): 包含存在于
setA
中但不存在于setB
中的元素。
“`javascript
const setA = new Set([1, 2, 3, 4]);
const setB = new Set([3, 4, 5, 6]);const differenceSet = new Set();
for (const element of setA) {
if (!setB.has(element)) { // Check if element from setA is NOT in setB
differenceSet.add(element);
}
}
console.log(“Difference (A – B):”, differenceSet); // Set(2) { 1, 2 }// 如果计算 B – A:
const differenceBminusA = new Set();
for (const element of setB) {
if (!setA.has(element)) {
differenceBminusA.add(element);
}
}
console.log(“Difference (B – A):”, differenceBminusA); // Set(2) { 5, 6 }
``
has()` 方法来实现。
差集操作同样可以通过迭代一个 Set 并利用另一个 Set 的 -
子集 (Subset): 检查
setA
是否是setB
的子集(即setA
中的所有元素都存在于setB
中)。
“`javascript
const setA = new Set([1, 2]);
const setB = new Set([1, 2, 3, 4]);
const setC = new Set([1, 5]);function isSubset(subset, superset) {
// If subset is larger than superset, it cannot be a subset
if (subset.size > superset.size) {
return false;
}
// Check if every element in subset exists in superset
for (const element of subset) {
if (!superset.has(element)) {
return false; // Found an element in subset that is not in superset
}
}
return true; // All elements in subset were found in superset
}console.log(“Is A a subset of B?”, isSubset(setA, setB)); // true
console.log(“Is C a subset of B?”, isSubset(setC, setB)); // false
console.log(“Is B a subset of A?”, isSubset(setB, setA)); // false (because B is larger)
``
has()` 方法检查其元素是否存在于潜在的父集中来实现。首先检查大小可以作为快速否定的优化。
检查子集可以通过遍历潜在的子集,并用
7.4 跟踪唯一项目 (Tracking Unique Items)
除了用户 ID,Set
还可以用于跟踪其他需要保持唯一性的项目,例如:
- 页面上所有独特的标签 (tags)。
- 在一个游戏中收集到的独特的物品列表。
- 某个操作中涉及到的所有独特的标识符。
“`javascript
const allTags = [“JavaScript”, “Web”, “Programming”, “JavaScript”, “ES6”, “Web”];
const uniqueTags = new Set(allTags);
console.log(“Unique tags:”, uniqueTags); // Set(4) { ‘JavaScript’, ‘Web’, ‘Programming’, ‘ES6’ }
if (uniqueTags.has(“HTML”)) {
console.log(“HTML tag found.”);
} else {
console.log(“HTML tag not found.”);
}
“`
8. Set 与其他数据结构的比较
特性 | Set | Array | Object (as Map) | Map |
---|---|---|---|---|
用途 | 存储唯一值的集合,快速查找元素 | 存储有序元素序列,通过索引访问 | 存储键值对,键为字符串或 Symbol | 存储键值对,键可以是任何类型 |
键/索引 | 没有键,值本身就是元素 | 数字索引 | 字符串或 Symbol | 任何类型 |
值 | 任何类型 | 任何类型 | 任何类型 | 任何类型 |
唯一性 | 值必须唯一 (SameValueZero) | 可以包含重复值 | 键必须唯一 (会被字符串化或 Symbol 比较) | 键必须唯一 (SameValueZero 比较) |
有序性 | 插入顺序 (非标准保证,但常见实现是如此) | 索引顺序 | 无序 (迭代顺序依赖实现,ES2015 后数字键和Symbol键按顺序,字符串键插入顺序) | 插入顺序 (迭代顺序有保证) |
查找 | set.has(value) (平均 O(1)) |
array.includes(value) (O(n)) |
obj.hasOwnProperty(key) 或 key in obj (平均 O(1)) |
map.has(key) (平均 O(1)) |
添加 | set.add(value) (平均 O(1)) |
array.push(value) (O(1) amortized) |
obj[key] = value (平均 O(1)) |
map.set(key, value) (平均 O(1)) |
删除 | set.delete(value) (平均 O(1)) |
array.splice(index, 1) (O(n)) |
delete obj[key] (平均 O(1)) |
map.delete(key) (平均 O(1)) |
大小 | set.size (O(1)) |
array.length (O(1)) |
手动计算或遍历 (O(n)) | map.size (O(1)) |
可迭代 | 是 (值) | 是 (值) | 是 (键/值/条目,需要特定方法) | 是 (键/值/条目) |
什么时候使用 Set?
- 你需要存储一个集合,并且集合中的元素必须是唯一的。
- 你需要快速地判断一个元素是否已经存在于集合中。
- 你需要执行数学集合运算(并集、交集等)。
什么时候使用 Array?
- 你需要存储一个有序的元素序列。
- 你需要通过索引访问或修改元素。
- 你需要对元素进行排序、过滤、映射等操作(Array 提供了大量内置方法)。
- 集合中允许存在重复元素。
什么时候使用 Object 或 Map?
- 你需要存储键值对。
Map
是 Object 的更现代和通用的替代品,允许使用任何类型作为键,并且迭代顺序有保证。Object
主要用于属性存储和结构化数据,但其键必须是字符串或 Symbol,且原型链可能会带来意外。
总的来说,Set
填补了 JavaScript 缺乏原生唯一值集合的空白,为处理特定类型的问题提供了优雅且高效的解决方案。
9. Set 的性能优势分析
正如前面提到的,Set
在进行查找 (has()
)、添加 (add()
) 和删除 (delete()
) 操作时,通常比数组具有显著的性能优势,尤其是在处理大量数据时。
这是因为 Set
在底层通常是基于哈希表(Hash Table)或类似的结构实现的。哈希表允许通过计算元素值的哈希码来直接定位元素在内存中的位置(或者在少数几个位置中查找),从而实现平均 O(1) 的常数时间复杂度。这意味着无论 Set 中有多少元素,查找、添加或删除一个元素所需的时间大致是相同的。
相比之下,数组进行 includes()
或 indexOf()
查找,或者通过值进行删除 (splice
配合查找),通常需要从头到尾遍历数组,时间复杂度是 O(n)。这意味着所需时间与数组的大小成正比。对于包含数千或数万个元素的数组,O(n) 的操作会变得非常慢。
何时 Set 不一定更快?
- 少量数据: 对于只有少量元素的集合,数组和 Set 之间的性能差异可以忽略不计,甚至数组的一些简单操作可能因为没有哈希计算的开销而略快。
- 遍历: 遍历 Set 的所有元素(例如使用
for...of
)的时间复杂度是 O(n),与遍历数组的时间复杂度相似。 - 创建 Set 从数组: 使用
new Set(array)
来从数组创建 Set 需要遍历整个数组并构建哈希结构,这本身就是 O(n) 的操作。所以,尽管创建 Set 是 O(n),但后续对这个 Set 的 O(1) 操作可能会弥补创建时的开销。 - 最坏情况(Hash Collision): 理论上,在哈希表的最坏情况下(所有元素的哈希码发生严重冲突),
Set
的操作可能会退化到 O(n)。但现代 JavaScript 引擎的实现会尽量避免这种情况,使其在实践中非常罕见。
总之,如果你主要的任务是管理一个需要保持唯一性的集合,并且频繁进行元素的添加、删除或存在性检查,那么 Set
是一个非常有吸引力的选择。
10. 进阶话题与注意事项
-
存储对象的引用: 再强调一次,
Set
存储的是对象的引用。如果你向Set
中添加一个对象,然后修改该对象的属性,这个修改不会影响该对象在Set
中的存在性或唯一性判断(因为引用没变)。但如果你创建了一个新对象,即使内容与 Set 中某个对象完全相同,has()
也会返回false
,尝试add()
也会添加一个新元素。
“`javascript
const person = { name: “Alice” };
const personSet = new Set([person]);console.log(personSet.has(person)); // true
// 修改对象属性
person.name = “Bob”;
console.log(person); // { name: ‘Bob’ }// Set 中仍然是同一个引用
console.log(personSet.has(person)); // true// 创建一个内容相同的新对象
const anotherPerson = { name: “Bob” };
console.log(personSet.has(anotherPerson)); // false
personSet.add(anotherPerson);
console.log(personSet.size); // 2 (存储了两个不同的对象引用)
console.log(personSet); // Set(2) { { name: ‘Bob’ }, { name: ‘Bob’ } } – Note: 内容相同,但引用不同
“` -
迭代顺序: 尽管规范没有强制要求,但主流 JavaScript 引擎(V8, SpiderMonkey 等)实现
Set
时是按照元素插入的顺序进行迭代的。这意味着当你使用for...of
或forEach
遍历Set
时,元素的出现顺序通常是你添加它们的顺序。然而,依赖这种顺序并不是一个好习惯,因为它不是标准的一部分,并且在某些边缘情况下或未来的引擎版本中可能会改变。将其视为无序集合更安全。 -
浏览器和环境支持:
Set
是 ES6 标准的一部分,因此在所有现代浏览器(Chrome, Firefox, Edge, Safari 的新版本)以及 Node.js 环境中都原生支持,无需 polyfill。
11. 结论
JavaScript Set
是一个非常实用的数据结构,它提供了存储唯一值集合的能力,并提供了高效的添加、删除和查找操作。理解其基于 SameValueZero 的唯一性判断规则,特别是对于对象类型的处理,是正确使用 Set
的关键。
从简单的数组去重到复杂的数学集合运算,Set
在多种场景下都能提供简洁高效的解决方案。与数组和 Map 相比,Set
专注于“值是否存在于集合中”这一核心需求,并在这一点上做得非常出色。
掌握 Set
的使用,将使你的 JavaScript 代码更加健洁、高效,并能更优雅地处理需要唯一性保障的数据集合。现在,就将 Set
融入你的日常开发实践中吧!