JavaScript Set 对象基础用法与数组去重实战
详细讲解 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
遍历 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 按插入顺序迭代的直接体现。
对象数组去重: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 完成按字段去重。思路不复杂,但需要明确去重依据的是哪个字段。
字符串去重与更多场景
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}
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²)。当数据量超过几千条时,两种方式的性能差距非常明显。
JS字符串转浮点数返回NaN时怎么判断和处理
2026-11-06 11:39:33
JS Set 遍历方法详解:forEach 与 for...of 的使用
2026-11-06 10:14:48
JS怎么去除字符串中的所有空格
2026-11-06 08:47:56
JS toFixed四舍五入精度偏差原因及精准保留两位小数方案
2026-11-05 22:36:46
JS数组一次性添加多个元素:push多参数与展开运算符技巧
2026-11-05 20:55:21






![JS创建二维数组:为什么Array(n).fill([])会导致数据联动](https://img.javascript.cn/tit/articles/13236/20261102/f618c991891ff37efb12df6af2ce0ccb0da8779a907fe1d2f7275d60c84fdd77.webp)

