new Set()
Set 是 ES6 引入的集合数据结构,通过 new Set() 创建实例,核心特点是存储唯一值(不重复),支持多种数据类型(原始类型按值比较,引用类型按地址比较),兼具数组和对象的部分特性,常用于去重、快速查找等场景。
一、基本语法与创建方式
1. 基本语法
// 1. 创建空 Set
const set = new Set();
// 2. 传入可迭代对象(数组、字符串、Map 等)初始化,自动去重
const set1 = new Set([1, 2, 2, 3]); // 数组初始化,自动去重 → Set(3) {1, 2, 3}
const set2 = new Set('hello'); // 字符串初始化(遍历字符)→ Set(4) {'h', 'e', 'l', 'o'}
const set3 = new Set(new Map([['a', 1], ['b', 2]])); // Map 初始化(取 entries)→ Set(2) {['a',1], ['b',2]}
2. 核心特性
- 唯一性:集合内不会有重复元素,添加重复值会被忽略;
- 可迭代性:支持
for...of遍历(底层基于 Iterator 协议); - 无键名,只有值:与对象不同,Set 存储的是 “值的集合”,不是键值对;
- 存储类型:可存储原始类型(Number、String、Symbol 等)和引用类型(对象、数组等);
- 顺序性:元素的存储顺序与添加顺序一致(插入顺序)。
二、Set 实例的核心方法
1. 增删改查相关方法
| 方法名 | 作用 | 返回值 | 示例 |
|---|---|---|---|
add(value) | 向集合添加元素(重复值忽略) | Set 实例(支持链式调用) | set.add(4).add(5) → 新增 4、5 |
delete(value) | 删除指定元素(成功返回 true,失败 false) | 布尔值 | set.delete(2) → true(删除 2 成功) |
has(value) | 判断集合是否包含指定元素 | 布尔值 | set.has(3) → true(包含 3) |
clear() | 清空集合所有元素 | undefined | set.clear() → 集合变为空 |
示例:增删改查
const set = new Set([1, 2, 3]);
// 添加元素(链式调用)
set.add(4).add(2); // 重复添加 2,被忽略 → Set {1,2,3,4}
// 判断是否包含元素
console.log(set.has(3)); // true
console.log(set.has(5)); // false
// 删除元素
console.log(set.delete(2)); // true → Set {1,3,4}
console.log(set.delete(5)); // false(无该元素)
// 清空集合
set.clear();
console.log(set); // Set(0) {}
2. 遍历相关方法(支持 4 种遍历方式)
Set 是可迭代对象,支持 for...of 直接遍历,也提供专门的遍历方法,且所有遍历方式的顺序都与插入顺序一致:
(1)forEach(callback[, thisArg]):按插入顺序遍历
const set = new Set(['a', 'b', 'c']);
// forEach 回调的前两个参数都是“当前值”(因 Set 无键名,复用参数位置)
set.forEach((value, key, set) => {
console.log(value, key); // 输出:a a → b b → c c
});
(2)keys():返回值的迭代器(与 values() 完全相同)
const keys = set.keys();
for (const key of keys) {
console.log(key); // 'a' → 'b' → 'c'
}
(3)values():返回值的迭代器(Set 的默认迭代器)
const values = set.values();
for (const value of values) {
console.log(value); // 'a' → 'b' → 'c'
}
// 简化:直接 for...of 遍历 Set(等价于遍历 values())
for (const value of set) {
console.log(value); // 'a' → 'b' → 'c'
}
(4)entries():返回 [value, value] 形式的迭代器(与 Map 对齐接口)
const entries = set.entries();
for (const entry of entries) {
console.log(entry); // ['a','a'] → ['b','b'] → ['c','c']
}
3. 其他属性
-
size:返回集合中元素的个数(类似数组的length):const set = new Set([1, 2, 3, 3]); console.log(set.size); // 3(去重后计数)
三、Set 的核心用途(高频场景)
1. 数组去重(最常用场景)
利用 Set 的 “唯一性”,结合扩展运算符 [...set] 或 Array.from(set),可快速实现数组去重(比 indexOf 或 filter 更简洁高效):
const arr = [1, 2, 2, 3, 3, 3];
// 方式 1:扩展运算符
const uniqueArr1 = [...new Set(arr)]; // [1, 2, 3]
// 方式 2:Array.from()
const uniqueArr2 = Array.from(new Set(arr)); // [1, 2, 3]
2. 字符串去重
同理,字符串可先转为 Set 去重,再转回字符串:
const str = 'aabbccdd';
const uniqueStr = [...new Set(str)].join(''); // 'abcd'
3. 快速查找元素(性能优于数组)
Set 的 has() 方法查找元素的时间复杂度是 O(1) (基于哈希表实现),而数组的 indexOf() 是 O(n) ,数据量越大,Set 的优势越明显:
const largeArr = new Array(1000000).fill(0).map((_, i) => i);
const largeSet = new Set(largeArr);
// 数组查找(慢)
console.time('array');
largeArr.indexOf(999999);
console.timeEnd('array'); // 约 10ms+
// Set 查找(快)
console.time('set');
largeSet.has(999999);
console.timeEnd('set'); // 约 0.1ms 以内
4. 实现集合运算(交集、并集、差集)
利用 Set 的特性,可简洁实现数学中的集合运算:
const setA = new Set([1, 2, 3, 4]);
const setB = new Set([3, 4, 5, 6]);
// 1. 并集(A ∪ B):所有元素,去重
const union = new Set([...setA, ...setB]); // Set {1,2,3,4,5,6}
// 2. 交集(A ∩ B):两个集合共有的元素
const intersection = new Set([...setA].filter(x => setB.has(x))); // Set {3,4}
// 3. 差集(A - B):A 有但 B 没有的元素
const difference = new Set([...setA].filter(x => !setB.has(x))); // Set {1,2}
5. 存储引用类型(注意地址唯一性)
Set 中引用类型(对象、数组)的 “唯一性” 基于内存地址,而非值本身。两个内容相同但地址不同的对象,会被视为不同元素:
const obj1 = { name: 'a' };
const obj2 = { name: 'a' }; // 与 obj1 地址不同
const set = new Set([obj1, obj2]);
console.log(set.size); // 2(视为两个不同元素)
console.log(set.has(obj1)); // true
console.log(set.has({ name: 'a' })); // false(新对象,地址不同)
四、Set 与 Array、Map 的区别
1. Set vs Array
| 特性 | Set | Array |
|---|---|---|
| 元素唯一性 | ✅(自动去重) | ❌(允许重复) |
| 查找效率 | O(1)(has()) | O(n)(indexOf()) |
| 插入 / 删除效率(中间位置) | O (1)(哈希表实现) | O (n)(需移动元素) |
| 核心用途 | 去重、快速查找、集合运算 | 有序存储、随机访问、遍历修改 |
2. Set vs Map
| 特性 | Set | Map |
|---|---|---|
| 存储结构 | 值的集合(value) | 键值对集合(key-value) |
| 元素唯一性 | 值唯一 | 键唯一(值可重复) |
| 遍历方式 | 直接遍历值(values ()/for...of) | 遍历键、值或 entries([key,value]) |
| 核心用途 | 去重、查找、集合运算 | 键值映射(如缓存、字典) |
五、注意事项
1. 原始类型的比较规则
Set 中原始类型的 “唯一性” 遵循 SameValueZero 算法(与 === 类似,但 NaN 视为相等):
const set = new Set([NaN, NaN, undefined, undefined, null, null]);
console.log(set.size); // 3(NaN 视为相同,undefined 和 null 各唯一)
console.log(NaN === NaN); // false,但 Set 中视为相同
2. 无法直接访问元素(无索引)
Set 没有数组那样的索引,无法通过 set[0] 访问元素,需通过遍历或转换为数组后访问:
const set = new Set([1, 2, 3]);
console.log(set[0]); // undefined(无索引)
// 解决:转为数组
const arr = [...set];
console.log(arr[0]); // 1
3. 引用类型的 “伪去重” 问题
如前所述,引用类型按地址去重,而非值去重。若需按值比较引用类型,需手动处理(如转为 JSON 字符串):
const objArr = [{ id: 1 }, { id: 1 }, { id: 2 }];
// 按 id 去重:先转为 JSON 字符串存入 Set,再转回对象
const uniqueObjArr = [...new Set(objArr.map(obj => JSON.stringify(obj)))]
.map(str => JSON.parse(str));
console.log(uniqueObjArr); // [{ id: 1 }, { id: 2 }]
4. 兼容性
Set 是 ES6 特性,支持 Chrome 38+、Firefox 13+、Safari 8+、Node.js 0.12+,低版本浏览器需通过 Babel + core-js polyfill 兼容。
六、总结
| 核心点 | 说明 |
|---|---|
| 本质 | 无重复值的有序集合(基于 Iterator) |
| 核心优势 | 自动去重、O (1) 查找 / 插入 / 删除、支持集合运算 |
| 高频用途 | 数组去重、字符串去重、快速查找、集合运算 |
| 注意点 | 无索引访问、引用类型按地址去重、NaN 视为相等 |
new Set() 是处理 “唯一值” 场景的高效工具,尤其适合去重、快速查找和集合运算,相比数组和对象,在特定场景下能显著提升代码简洁性和性能。
new Set() 怎么用于对象数组
new Set() 绝非只能用于数组去重!它的核心是 “存储唯一值”,支持任何数据类型(包括对象、数组等引用类型)—— 但对象数组的去重逻辑和原始类型数组不同,需要特别注意 Set 的 “唯一性判断规则”。
一、先明确结论
new Set()可以直接存储对象数组,但默认无法实现 “值层面去重” (只能实现 “引用层面去重”);- 若要对对象数组做 “值去重”(比如两个
{ id: 1 }视为同一个对象),需要手动处理(转换为原始类型后去重,再转回对象)。
二、为什么对象数组不能直接用 Set 去重?
Set 判断 “值是否重复” 的规则是 SameValueZero 算法:
- 原始类型(数字、字符串、Symbol 等):按 “值” 比较(
NaN视为相等); - 引用类型(对象、数组、函数等):按 “内存地址” 比较(两个内容完全相同但地址不同的对象,会被视为不同元素)。
示例:直接用 Set 存储对象数组(引用去重,而非值去重)
const objArr = [
{ id: 1, name: '张三' },
{ id: 1, name: '张三' }, // 内容相同,但内存地址不同
{ id: 2, name: '李四' },
{ id: 1, name: '张三' } // 又一个新地址的相同内容对象
];
// 直接用 Set 存储对象数组
const set = new Set(objArr);
console.log(set.size); // 3(而非 2!因为三个 {id:1} 是不同地址的对象)
console.log([...set]); // 原数组的 3 个对象(未去重)
- 原因:
{ id: 1, name: '张三' }每次创建都是新的内存地址,Set认为它们是不同值,所以无法自动去重。
三、对象数组用 Set 实现 “值去重” 的正确方法
要实现 “内容相同的对象视为重复”,核心思路是:先将对象转换为 “唯一的原始类型标识”(如 JSON 字符串),用 Set 对标识去重,再将标识转回对象。
方法 1:JSON.stringify () 转换(简单场景首选)
利用 JSON.stringify(obj) 将对象转为字符串(内容相同的对象会生成相同字符串),去重后再用 JSON.parse() 转回对象。
示例:根据对象全部属性去重
const objArr = [
{ id: 1, name: '张三' },
{ id: 1, name: '张三' }, // 内容相同 → 字符串相同
{ id: 2, name: '李四' },
{ id: 1, name: '张三' }
];
// 步骤:对象 → 字符串 → Set 去重 → 转回对象
const uniqueObjArr = [...new Set(objArr.map(obj => JSON.stringify(obj)))]
.map(str => JSON.parse(str));
console.log(uniqueObjArr);
// 输出:[{ id: 1, name: '张三' }, { id: 2, name: '李四' }](成功去重)
示例:根据对象某一个属性去重(如 id 唯一)
如果只需按某个关键字段(如 id)去重,无需考虑其他属性,可简化转换逻辑:
const objArr = [
{ id: 1, name: '张三' },
{ id: 1, name: '张三2' }, // id 相同,视为重复
{ id: 2, name: '李四' }
];
// 步骤:按 id 生成唯一标识 → Set 去重 → 筛选原数组
const uniqueIds = new Set(objArr.map(obj => obj.id)); // 存储唯一 id
const uniqueObjArr = objArr.filter((obj, index) => {
// 只保留第一次出现该 id 的对象
return objArr.findIndex(item => item.id === obj.id) === index;
});
console.log(uniqueObjArr);
// 输出:[{ id: 1, name: '张三' }, { id: 2, name: '李四' }]
方法 2:Map 辅助去重(更灵活,支持复杂场景)
用 Map 存储 “唯一标识 → 对象” 的映射,比 Set+JSON.stringify 更高效(避免字符串解析开销),且支持自定义去重规则。
示例:按 id 去重(保留最后一次出现的对象)
const objArr = [
{ id: 1, name: '张三' },
{ id: 1, name: '张三更新版' }, // 保留这个(最后一次出现)
{ id: 2, name: '李四' }
];
const map = new Map();
objArr.forEach(obj => {
// 用 id 作为唯一 key,重复 key 会覆盖,保留最后一个对象
map.set(obj.id, obj);
});
const uniqueObjArr = [...map.values()]; // Map 的 values() 是去重后的对象
console.log(uniqueObjArr);
// 输出:[{ id: 1, name: '张三更新版' }, { id: 2, name: '李四' }]
四、注意事项(避免踩坑)
1. JSON.stringify () 的局限性
- 不支持函数、Symbol、循环引用等:如果对象包含
function、Symbol、undefined或循环引用(如obj.self = obj),JSON.stringify会忽略这些属性或报错; - 属性顺序影响结果:
{ a: 1, b: 2 }和{ b: 2, a: 1 }会生成不同字符串,被视为不同对象(可先排序属性再转换)。
解决:复杂对象的去重方案
如果对象包含特殊属性,可自定义 “唯一标识生成函数”,而非依赖 JSON.stringify:
// 自定义标识生成:按 id + name 组合(忽略属性顺序)
function getUniqueKey(obj) {
return `${obj.id}-${obj.name}`; // 确保相同内容生成相同 key
}
const objArr = [
{ id: 1, name: '张三', age: 25 },
{ name: '张三', id: 1, age: 25 }, // 属性顺序不同,但 key 相同
];
const uniqueObjArr = [...new Set(objArr.map(getUniqueKey))]
.map(key => objArr.find(obj => getUniqueKey(obj) === key));
2. 引用类型的 “浅拷贝” 问题
去重后的对象仍是原数组的引用(JSON.parse(JSON.stringify(obj)) 除外,它是深拷贝),修改去重后的对象会影响原数组:
const objArr = [{ id: 1, name: '张三' }];
const uniqueObjArr = [...new Set(objArr.map(JSON.stringify))].map(JSON.parse);
uniqueObjArr[0].name = '李四';
console.log(objArr[0].name); // 张三(JSON.parse 是深拷贝,不影响原对象)
// 对比:Map 方法是浅拷贝
const map = new Map();
objArr.forEach(obj => map.set(obj.id, obj));
const uniqueObjArr2 = [...map.values()];
uniqueObjArr2[0].name = '王五';
console.log(objArr[0].name); // 王五(修改了原对象引用)
五、new Set () 的其他用途(不止数组去重!)
回到最初的问题:new Set() 绝非只能数组去重,它的核心是 “唯一值集合”,还有这些高频用途:
- 字符串去重:
[...new Set('aabbcc')].join('') → 'abc'; - 快速查找:
set.has(value)时间复杂度 O (1),比数组indexOf快(O (n)); - 集合运算:实现交集、并集、差集(如
new Set([...setA].filter(x => setB.has(x)))); - 存储不重复的引用类型:如存储不重复的 DOM 元素、类实例等(按引用去重)。
示例:存储不重复的 DOM 元素
const div1 = document.getElementById('div1');
const div2 = document.getElementById('div2');
const div1Again = document.getElementById('div1'); // 同一 DOM 元素(相同引用)
const set = new Set([div1, div2, div1Again]);
console.log(set.size); // 2(div1Again 和 div1 是同一引用,自动去重)
六、总结
new Set()支持对象数组,但默认按 “引用” 去重,需手动处理才能按 “值” 去重;- 对象数组去重的核心:将对象转为 “唯一原始标识”(如 JSON 字符串、key 组合),去重后再转回对象;
new Set()的用途远不止数组去重,还包括字符串去重、快速查找、集合运算、存储不重复引用类型等。
实际开发中,对象数组去重更推荐用 Map 辅助(高效、灵活),简单场景可用 JSON.stringify,复杂对象需自定义去重规则。