JavaScript Set 对象基础用法与数组去重实战

作者:半夏时间:2026-11-06 10:54:56浏览:93

详细讲解 JavaScript Set 对象的核心用法,包括创建、增删查操作、遍历方式,以及利用 Set 进行数组去重的多种实战方法和注意事项。

Set 是 ES6 引入的一种集合数据结构,它的核心特性只有一个:成员值唯一,不允许重复。利用这个特性,可以用极少的代码完成数组去重;而了解 Set 本身的增删查遍历操作,也是日常开发中经常用到的基本功。本文从 Set 的创建和常用方法讲起,再通过多个实战示例演示数组去重的具体写法和需要注意的细节。

创建 Set 与基本特性

通过 new Set() 即可创建一个空集合。如果传入一个可迭代对象(数组、字符串等),Set 会自动将其中的元素逐个添加,遇到重复值时自动忽略。

// 创建空 Set
const s1 = new Set();

// 从数组创建,重复元素自动去除
const s2 = new Set([1, 2, 3, 2, 1]);
console.log(s2);       // Set(3) {1, 2, 3}
console.log(s2.size);  // 3

// 从字符串创建,每个字符作为一个成员
const s3 = new Set('hello');
console.log(s3);       // Set(4) {'h', 'e', 'l', 'o'}

Set 判断重复使用的算法类似于 ===,但有一个例外:NaN 等于 NaN。在 Set 内部,两个 NaN 被视为相同的值,只会保留一个。

const s = new Set([NaN, NaN, 0, -0]);
console.log(s);       // Set(2) {NaN, 0}
// 注意:0 和 -0 在 Set 中也被视为相同

常用方法:增、删、查

Set 提供了四个最常用的操作方法,接口简洁,不容易用错。

方法作用返回值
add(value)添加一个值Set 本身(可链式调用)
delete(value)删除指定值布尔值,表示是否删除成功
has(value)判断是否包含某值布尔值
clear()清空所有成员无
const colors = new Set();

// add 返回 Set 本身,所以可以链式调用
colors.add('red').add('green').add('blue');
console.log(colors.size);        // 3

// 重复添加不会报错,但也不会生效
colors.add('red');
console.log(colors.size);        // 仍然是 3

// has 检查成员是否存在
console.log(colors.has('green'));  // true
console.log(colors.has('yellow')); // false

// delete 删除指定值
colors.delete('green');
console.log(colors.has('green'));  // false

// clear 清空
colors.clear();
console.log(colors.size);         // 0
JavaScript Set 对象基础用法与数组去重实战
图 1:Set 对象核心方法的调用流程与返回值

遍历 Set

Set 维护元素的插入顺序,提供了多种遍历方式。由于 Set 没有键名(或者说键名和键值相同),keys() 和 values() 返回的结果完全一样。

const fruits = new Set(['apple', 'banana', 'cherry']);

// for...of 直接遍历
for (const item of fruits) {
  console.log(item);
}
// apple
// banana
// cherry

// forEach 方法
fruits.forEach((value, key) => {
  console.log(key, value);
  // key 和 value 始终相同
});

// 展开运算符转为数组
const arr = [...fruits];
console.log(arr);  // ['apple', 'banana', 'cherry']

entries() 方法返回的每一对都是 [value, value] 的形式,这是为了保持和 Map 接口的一致性。实际使用中很少用到它,直接用 for...of 或展开运算符就够了。

数组去重:最简写法

把数组传给 Set 构造函数去掉重复值,再用展开运算符转回数组——这是目前最简洁的去重写法,一行就能搞定。

const raw = [1, 2, 3, 4, 2, 3, 1, 5];
const unique = [...new Set(raw)];
console.log(unique);  // [1, 2, 3, 4, 5]

也可以用 Array.from() 代替展开运算符,效果相同:

const unique2 = Array.from(new Set(raw));
console.log(unique2); // [1, 2, 3, 4, 5]

去重后的元素保持原数组中首次出现的顺序,这也是 Set 按插入顺序迭代的直接体现。

JavaScript Set 对象基础用法与数组去重实战
图 2:数组经过 Set 去重再转回数组的完整步骤

对象数组去重:Set 做不到的事

Set 的去重基于值的严格相等比较。对于对象(包括普通对象、数组),即使内容完全相同,只要引用不同就是不同的值。这意味着 Set 无法直接对"内容相同但引用不同的对象"去重。

const arr = [
  { id: 1, name: '张三' },
  { id: 2, name: '李四' },
  { id: 1, name: '张三' }  // 内容相同,但是新对象
];

const s = new Set(arr);
console.log(s.size);  // 3,三个对象引用不同,全部保留

遇到这种场景,需要自己定义"相同"的标准。常用做法是先用 Map 或一个辅助对象按指定字段记录已出现的值:

function uniqueById(arr) {
  const seen = new Set();
  return arr.filter(item => {
    if (seen.has(item.id)) return false;
    seen.add(item.id);
    return true;
  });
}

const result = uniqueById(arr);
console.log(result);
// [{ id: 1, name: '张三' }, { id: 2, name: '李四' }]

这里用 Set 记录已出现的 id,配合 filter 完成按字段去重。思路不复杂,但需要明确去重依据的是哪个字段。

JavaScript Set 对象基础用法与数组去重实战
图 3:基本类型与引用类型在 Set 去重中的差异及解决方案

字符串去重与更多场景

Set 的去重能力不限于数字数组。对字符串数组同样有效,对单个字符串也可以用来提取不重复的字符。

// 字符串数组去重
const tags = ['前端', '后端', '前端', 'JavaScript', '后端'];
const uniqueTags = [...new Set(tags)];
console.log(uniqueTags);  // ['前端', '后端', 'JavaScript']

// 字符串去重(提取不重复字符)
const chars = [...new Set('abracadabra')].join('');
console.log(chars);  // 'abrcd'

在实际项目中,Set 也常用来做"是否已存在"的快速判断。比如处理事件时判断某个元素是否已经被选中,或在请求数据后标记已加载的 id 列表。has() 的查找效率远高于数组的 indexOf 或 includes——数组是线性查找 O(n),Set 内部使用哈希结构,平均时间复杂度为 O(1)。

Set 与数组互转

Set 和数组之间可以自由转换,这也为两者的方法互补提供了便利。Set 没有 map、filter、reduce 等方法,但转成数组后就能使用全部数组方法,处理完再转回 Set。

const s = new Set([1, 2, 3, 4, 5]);

// Set → 数组,使用数组方法过滤偶数
const even = [...s].filter(x => x % 2 === 0);
console.log(even);  // [2, 4]

// 用 map 对 Set 中的值做变换
const doubled = new Set([...s].map(x => x * 2));
console.log(doubled);  // Set(5) {2, 4, 6, 8, 10}

用 Set 实现集合运算

数学中集合的交集、并集、差集操作,用 Set 配合数组方法都可以简洁实现。

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

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

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

// 差集(a 有 b 没有)
const diff = new Set([...a].filter(x => !b.has(x)));
console.log(diff);         // Set(2) {1, 2}
ES2025 已经为 Set 新增了原生的 union()、intersection()、difference()、symmetricDifference() 等方法,主流浏览器和 Node.js 22+ 已经支持。如果运行环境允许,可以直接使用 a.union(b)、a.intersection(b) 等方法,无需手动展开计算。

WeakSet:弱引用版本

JavaScript 还提供了 WeakSet,它和 Set 的区别有三点:只能存放对象、对成员是弱引用(不阻止垃圾回收)、不可遍历。WeakSet 常用于标记对象是否被处理过,例如防止递归遍历时重复访问同一对象。

const visited = new WeakSet();

function traverse(obj) {
  if (visited.has(obj)) return; // 已经访问过,跳过
  visited.add(obj);
  // 处理 obj ...
}

由于 WeakSet 不可枚举也没有 size 属性,它不适合用于去重这类需要获取结果集的场景,但在内存管理方面比 Set 更优——当对象被其他地方释放后,WeakSet 中的引用会自动消失,不会造成内存泄漏。

注意事项与常见问题

去重不改变原数组。 [...new Set(arr)] 总是返回新数组,原数组不受影响。如果需要就地修改,需手动赋值回去。

类型不会自动转换。 Set 中 1 和 '1' 是两个不同的值。如果数据中混有数字和对应的字符串形式,去重后两个都会保留。

const mixed = new Set([1, '1', true, 'true']);
console.log(mixed.size);  // 4,四个值全部不同
使用 JSON.stringify 将对象转为字符串后放进 Set 来做"深度去重"是一种常见的变通方法,但要注意:JSON.stringify 对属性顺序不同的对象可能产生不同结果,且无法处理 undefined、函数等不可序列化的值。对于复杂场景,建议按业务字段去重或使用专门的深比较工具。

性能表现。 Set 的 has、add、delete 平均时间复杂度为 O(1),去重一个长度为 n 的数组,整体时间复杂度为 O(n)。相比之下,用 indexOf 的双重循环去重是 O(n²)。当数据量超过几千条时,两种方式的性能差距非常明显。

JavaScript Set 对象基础用法与数组去重实战
图 4:JavaScript Set 对象知识要点总结
作者最新文章
相关文章
更多