前言

在前端开发中,我们经常需要处理去重、集合运算、唯一性校验等需求。过去我们通常使用 Array 配合 filter + indexOfreduce 来实现,但代码冗长且性能不佳。

为了解决这类问题,ES6 引入了 Set 数据结构

本文将带你全面掌握 Set定义、特性、API、使用场景、性能对比与最佳实践,助你在项目中高效处理“唯一性”问题。


一、什么是 Set?

定义

Set 是一个 唯一值的集合,它允许存储任何类型的值,且每个值在集合中只出现一次。

const set = new Set();

核心特性

特性 说明
自动去重 重复添加的值会被忽略
大小可查 set.size 直接获取元素数量
有序存储 按插入顺序遍历
无原型污染 不继承 Object.prototype,更“干净”
性能更优 查找、插入、删除操作平均时间复杂度为 O(1)

二、Set 的基本用法

1. 创建 Set

// 空 Set
const set = new Set();

// 从数组初始化(自动去重)
const set = new Set([1, 2, 3, 2, 1]); // Set { 1, 2, 3 }

// 从字符串创建(字符去重)
const charSet = new Set('hello'); // Set { 'h', 'e', 'l', 'o' }

2. 增删查 API

方法 说明 返回值
add(value) 添加值 Set 本身(可链式调用)
has(value) 判断值是否存在 boolean
delete(value) 删除值 boolean(是否删除成功)
clear() 清空所有 void
const userSet = new Set();

userSet.add('Alice');
userSet.add('Bob');
userSet.add('Alice'); // 重复,忽略

console.log(userSet.has('Alice')); // true
console.log(userSet.delete('Bob')); // true
console.log(userSet.size); // 1
userSet.clear();
console.log(userSet.size); // 0

3. 链式调用

new Set()
  .add(1)
  .add(2)
  .add(3);

三、Set 的遍历方式

Set 提供了三种遍历方法,返回 迭代器(Iterator)

方法 返回值 说明
keys() 值的迭代器 values() 相同(兼容 Map)
values() 值的迭代器 默认遍历方式
entries() 键值对的迭代器 每个元素是 [value, value]

1. for...of 遍历

const set = new Set(['a', 'b', 'c']);

// 遍历值
for (const value of set) {
  console.log(value);
}
// a
// b
// c

// 或使用 values()
for (const value of set.values()) {
  console.log(value);
}

// 遍历键值对(每个键值相同)
for (const [key, value] of set.entries()) {
  console.log(key, value); // a a, b b, c c
}

2. forEach 遍历

set.forEach((value, key) => {
  console.log(value); // 注意:key === value
});

注意:forEach 的参数中 keyvalue 相同,这是为了与 Map 保持一致。


四、Set 与 Array 的对比

特性 Set Array
唯一性 自动去重 允许重复
大小 set.size arr.length
顺序 插入顺序 插入顺序
查找性能 O(1) O(n)
序列化 需手动处理 JSON.stringify() 支持
语法 set.has(value) arr.includes(value)

何时使用 Set?

场景 推荐
去重 Set(最优雅)
频繁查找是否存在 Set(性能优)
需要保持唯一性 Set
需要索引访问(arr[0] Array
需要 mapfilter 等方法 Array(需转换)
数据量小、操作简单 可任选

五、Set 的高级用法

1. 数组去重(最简洁写法)

const arr = [1, 2, 2, 3, 3, 4];
const uniqueArr = [...new Set(arr)];
// [1, 2, 3, 4]

// 或
const uniqueArr = Array.from(new Set(arr));

2. 集合运算(交集、并集、差集)

const a = new Set([1, 2, 3]);
const b = new Set([2, 3, 4]);

// 并集
const union = new Set([...a, ...b]); // {1, 2, 3, 4}

// 交集
const intersection = new Set([...a].filter(x => b.has(x))); // {2, 3}

// 差集(a - b)
const difference = new Set([...a].filter(x => !b.has(x))); // {1}

3. 数组元素唯一性校验

const hasDuplicates = (arr) => {
  return new Set(arr).size !== arr.length;
};

hasDuplicates([1, 2, 2]); // true
hasDuplicates([1, 2, 3]); // false

4. 转换为 Array / Object

const set = new Set(['a', 'b', 'c']);

// Set → Array
const arr = [...set]; // ['a', 'b', 'c']
// 或 Array.from(set)

// Array → Set
const newSet = new Set(arr);

六、WeakSet:更轻量的 Set

什么是 WeakSet?

  • 值必须是 对象
  • 值是 弱引用,不会阻止垃圾回收
  • 不能遍历,没有 sizeclear()
  • 用于对象标记、私有数据、缓存
const ws = new WeakSet();

const obj = { name: 'Alice' };
ws.add(obj);

console.log(ws.has(obj)); // true

// 当 obj 被销毁,WeakSet 中的引用也会被自动清除

与 Set 对比

特性 Set WeakSet
值类型 任意 对象
弱引用 不支持 支持
可遍历 支持 不支持
size / clear() 支持 不支持
用途 通用唯一集合 对象标记、缓存、私有数据

七、常见问题

1. SetArray 去重的区别?

答:

  • Set 去重基于 SameValueZero 算法,NaN === NaN
  • ArrayindexOf 使用 ===NaN !== NaN,无法去重 NaN
  • Set 性能更好,代码更简洁。

2. Set 如何判断重复?

答:使用 SameValueZero 比较算法:

  • === 大部分情况
  • 特殊:NaN 被认为相等
const set = new Set([NaN, NaN]);
console.log(set.size); // 1

3. WeakSet 有什么用?

答:用于对象的临时标记、私有数据存储、避免内存泄漏。例如:

const disabledElements = new WeakSet();
disabledElements.add(button);
// 当 button 被移除,标记自动消失

4. 如何实现对象内容去重?

答:需手动实现,如将对象序列化为字符串:

const objSet = new Set();
const key = JSON.stringify({ id: 1 });
objSet.add(key);

注意顺序、类型、undefined 处理。


总结

要点 说明
核心优势 自动去重、查找快(O(1))、有序、可查大小
适用场景 数组去重、集合运算、唯一性校验、缓存键
遍历方式 for...offorEachvalues/entries
转换方法 [...set]Array.from(set)
内存优化 WeakSet 用于对象的弱引用场景

更多推荐