“Set”对象

“Set”对象

本文解释“Set”对象。

我们将通过实用示例来讲解“Set”对象。

YouTube Video

“Set”对象

“Set”是一个用于处理唯一值集合(不含重复值)的内置对象。它比数组更简单地实现去重和判断是否存在等操作,也更容易实现集合的并集、交集等集合操作。

基础:创建和使用Set

首先,我们来看如何创建一个Set、添加和删除元素、检查元素是否存在以及获取其大小。

下面是创建新Set并演示“add”、“has”、“delete”和“size”的基本用法。

 1// Create a Set and demonstrate add, has, delete, and size
 2const s = new Set();
 3
 4s.add(1);
 5s.add(2);
 6s.add(2); // duplicate, ignored
 7
 8console.log(s.has(1)); // true
 9console.log(s.has(3)); // false
10
11s.delete(2);
12console.log(s.size); // 1
13
14console.log([...s]); // [1]
  • 如本代码所示,Set 会自动去除重复的原始值,并且可以使用 size 获取元素的数量。

遍历方法

Set 是可迭代的,因此你可以使用 for...offorEach 对其进行遍历。遍历顺序为插入顺序。

下面是“for...of”和“forEach”的常见用法。

 1// Iterate a Set with for...of and forEach
 2const s = new Set(['a', 'b', 'c']);
 3
 4for (const v of s) {
 5  console.log('for...of:', v);
 6}
 7
 8s.forEach((value, sameValue, setRef) => {
 9  // Note: second arg is same as first for Set API to match Map signature
10  console.log('forEach:', value);
11});
  • Set的“forEach”回调签名为“value, value, set”(为兼容Map),但实际用时通常只用第一个“value”参数。

数组与Set的转换(数组去重的常用方法)

下面展示如何用Set简单去除数组重复元素,以及如何将Set再转为数组。

以下示例展示了如何将数组传给Set以去除重复元素。

1// Deduplicate an array using Set
2const arr = [1, 2, 2, 3, 3, 3];
3const deduped = [...new Set(arr)];
4console.log(deduped); // [1, 2, 3]
5
6// Convert a Set to an array using Array.from
7const s = new Set([4, 5, 6]);
8const arrFromSet = Array.from(s);
9console.log(arrFromSet); // [4, 5, 6]
  • 这种写法又简短又高效,因此数组去重时常用。这个方法对于原始值效果特别好。

对象与引用的处理

Set中的对象是引用比较,所以即使内容相同,不同的实例也被视为不同元素。

下面代码演示了将对象添加到Set时发生的情况。

 1// Objects are compared by reference in a Set
 2const obj1 = { x: 1 };
 3const obj2 = { x: 1 };
 4
 5const s = new Set();
 6s.add(obj1);
 7s.add(obj2);
 8
 9console.log(s.size); // 2 (different references)
10console.log(s.has(obj1)); // true
11console.log(s.has({ x: 1 })); // false (different object)
  • 对象的重复检测基于引用标识,如果想只按内容去重,需要将对象序列化或作其他处理。

特殊值:如何处理NaN和-0/+0

Set采用Same-value-zero(同值零)比较规则判断值是否相等。该比较方法在数字上有以下特点:。

  • NaN被视为等于NaN。
  • 正零(+0)和负零(-0)不作区分,视为同一个值。

因此,将这些值加入Set时会出现如下行为:。

 1// NaN and zero behavior in Set
 2const s = new Set();
 3
 4s.add(NaN);
 5s.add(NaN);
 6console.log(s.size); // 1 (NaN considered the same)
 7
 8s.add(+0);
 9s.add(-0);
10console.log(s.size); // still 2 (NaN + 0)
11console.log([...s]); // [NaN, 0] (order may vary but only one zero)
  • 通常比较中(NaN === NaN)结果为false,但在Set中,所有NaN视为相同的值。
  • +0和-0在数学上可区分,但在Set中视为同一个0。
  • 结果是,Set里只保留一个NaN和一个0。
  • Set的比较规则与Object.is类似,但并不完全相同。Object.is(+0, -0)会返回false,但在Set中二者视为相同。请注意这一差异。

常用功能:集合操作(并集、交集、差集)

利用Set,集合操作可以写得更简洁明了。下面是常见的实现示例。

这里是“并集”、“交集”和“差集”函数的例子。

 1// Set operations: union, intersection, difference
 2function union(a, b) {
 3  return new Set([...a, ...b]);
 4}
 5
 6function intersection(a, b) {
 7  return new Set([...a].filter(x => b.has(x)));
 8}
 9
10function difference(a, b) {
11  return new Set([...a].filter(x => !b.has(x)));
12}
13
14// Demo
15const A = new Set([1, 2, 3]);
16const B = new Set([3, 4, 5]);
17
18console.log('union', [...union(A, B)]); // [1,2,3,4,5]
19console.log('intersection', [...intersection(A, B)]); // [3]
20console.log('difference A\\B', [...difference(A, B)]); // [1,2]
  • 结合Set与数组,用filter就能简单实现集合操作。当处理大型数据集时,Set的has操作具有O(1)的效率,使操作更快。

实用示例:找出数组的差异(检测新增/移除的元素)

下面的例子展示如何用Set找出两个数组(如旧列表与新列表)之间的差异。这样可以识别出哪些元素被新增、哪些被移除。

 1// Find added and removed items between two arrays
 2function diffArrays(oldArr, newArr) {
 3  const oldSet = new Set(oldArr);
 4  const newSet = new Set(newArr);
 5
 6  const added = [...newSet].filter(x => !oldSet.has(x));
 7  const removed = [...oldSet].filter(x => !newSet.has(x));
 8
 9  return { added, removed };
10}
11
12const oldList = [1, 2, 3];
13const newList = [2, 3, 4, 5];
14
15console.log(diffArrays(oldList, newList));
16// { added: [4,5], removed: [1] }
  • 这种方式在检测ID列表、标签列表等差异时非常方便。在原始值场景下最为简便。

WeakSet与Set的区别(内存管理)

WeakSet 类似于 Set,但它使用弱引用,使其元素可以被垃圾回收。下面演示WeakSet的基本用法。

1// WeakSet basics (objects only, not iterable)
2const ws = new WeakSet();
3let obj = { id: 1 };
4ws.add(obj);
5
6console.log(ws.has(obj)); // true
7
8obj = null; // Now the object is eligible for GC; WeakSet won't prevent collection

WeakSet只能存放对象,且不可迭代。下面是WeakSet的限制示例——它只能保存对象,且无法遍历。

 1// WeakSet basics (objects only, not iterable)
 2const ws = new WeakSet();
 3
 4// --- Only objects can be added ---
 5try {
 6	ws.add(1); // number
 7} catch (e) {
 8	console.log("Error: WeakSet can only store objects. Adding a number is not allowed.");
 9}
10
11try {
12	ws.add("text"); // string
13} catch (e) {
14	console.log("Error: WeakSet can only store objects. Adding a string is not allowed.");
15}
16
17// --- WeakSet is not iterable ---
18try {
19	for (const value of ws) {
20		console.log(value);
21	}
22} catch (e) {
23	console.log("Error: WeakSet is not iterable. You cannot use for...of to loop over its elements.");
24}
25
26// --- Cannot convert to array ---
27try {
28	console.log([...ws]);
29} catch (e) {
30	console.log("Error: WeakSet cannot be converted to an array because it does not support iteration.");
31}
32
33// The object becomes eligible for garbage collection
34let obj = { id: 1 };
35ws.add(obj);
36obj = null;
  • WeakSet适合临时跟踪对象是否存在,但不能枚举其元素,也无法获得其大小。

性能与使用场景选择

决定是否使用Set时,要了解它的性能特点以及数据的性质。

  • hasadddelete操作平均具有几乎O(1)的性能。所以在频繁判断元素是否存在或去重的场景下,Set通常比数组更有优势。
  • 如果希望按内容(值)去重对象时要注意。由于Set用引用比较,实际中可以用ID或其他键,或在需要按值比较时,把对象序列化为原始值后再用Set。
  • Set对于小到中等规模集合提升代码可读性特别有帮助。另外,如果要处理大量元素或频繁数组、Set相互转换,建议实际进行性能测试

常见的陷阱

Set很方便,但如果不了解其详细规则,可能会遇到一些意外行为导致困惑。以下是一些典型的注意事项:。

  • 对象是引用比较,即使内容相同,不同对象不会被视为重复。
  • Set保持插入顺序,但无法像数组那样按索引访问元素。如需索引式访问,请先将Set转为数组。
  • WeakSet无法枚举只能存储对象。请注意它的应用场景受限。
  • NaN会视为相同,+0和-0不会区分。这是由于Same-value-zero比较规则导致的。

总结

Set是一种非常方便的数据结构,可以直观地处理唯一值的集合。你可以用它去除数组重复项、快速判断元素存在,还能用简单易读的代码实现并集等集合操作。

但另一方面,由于对象是引用比较,如果希望按内容判断对象相等,需要额外措施。

只要理解并合理使用这些特性,Set能大幅提升代码的可读性和可维护性。

您可以在我们的YouTube频道上使用Visual Studio Code跟随上述文章进行学习。 请也查看我们的YouTube频道。

YouTube Video