数据的“洁癖”管家:深入解析 JavaScript Set
🧊 数据的“洁癖”管家:深入解析 JavaScript Set
🤔 为什么我们需要 Set?
在开发中,我们经常遇到这样的场景:
- 数组去重:从后端获取了一堆标签,需要去除重复项。
- 快速查找:判断某个用户 ID 是否已经在黑名单中。
- 集合运算:求两个用户群体的交集、并集或差集。
如果使用传统的Array,这些操作往往需要嵌套循环或复杂的逻辑,代码冗长且性能低下(尤其是数据量大时)。而Set天生就是为了解决“唯一性”和“高效查找”而生的。
通俗比喻:
Array(数组):像是一个普通的储物箱。你可以往里面扔任何东西,哪怕是一模一样的苹果,它也会照单全收。如果你想找某个苹果,你得把箱子倒出来一个个看。Set(集合):像是一个带有自动识别功能的智能货架。
- 唯一性:如果你试图放入一个已经存在的苹果,货架会直接拒绝:“这个已经有了!”
- 高效性:货架内部有索引,问你“有没有红富士?”它能瞬间回答“有”或“没有”,不需要逐个翻找。
📂 目录
- 🔍 核心概念与基本用法
- 🛠️ 常用 API 详解
- ⚔️ 实战场景:去重与集合运算
- 💡 进阶技巧:WeakSet 与性能分析
- ❌ 常见误区与坑点
- 💡 总结与选型建议
1. 🔍 核心概念与基本用法
Set本身是一个构造函数,用来生成Set数据结构。
✅ 基本创建
// 1. 创建一个空的 Setconsts1=newSet();// 2. 通过数组初始化(自动去重)consts2=newSet([1,2,3,2,1]);console.log(s2);// Set(3) { 1, 2, 3 }// 3. 通过字符串初始化(字符串也是可迭代对象)consts3=newSet("hello");console.log(s3);// Set(4) { 'h', 'e', 'l', 'o' } -> 注意 'l' 只保留了一个⚠️ 判断相等的规则
Set内部判断两个值是否相等,使用的是“Same-value-zero”算法。
- 它类似于严格相等运算符 (
===)。 - 主要区别:在
Set中,NaN等于NaN。
consts=newSet();s.add(NaN);s.add(NaN);console.log(s.size);// 1,因为 NaN === NaN 在 Set 中被视为相同s.add({});s.add({});console.log(s.size);// 3,因为两个空对象引用地址不同,被视为不同元素2. 🛠️ 常用 API 详解
Set的实例方法非常直观,主要分为增、删、查、清四类。
| 方法 | 描述 | 返回值 |
|---|---|---|
add(value) | 添加某个值,返回 Set 结构本身(支持链式调用) | Set对象 |
delete(value) | 删除某个值,返回一个布尔值,表示删除是否成功 | boolean |
has(value) | 判断某个值是否存在于 Set 中 | boolean |
clear() | 清除所有成员,没有返回值 | undefined |
size | 属性,返回成员总数 | number |
💻 代码示例
constmySet=newSet();// 添加元素mySet.add(1).add(2).add(2);// 链式调用,第二个 2 被忽略console.log(mySet.size);// 2// 判断存在console.log(mySet.has(1));// trueconsole.log(mySet.has(3));// false// 删除元素mySet.delete(1);console.log(mySet.has(1));// false// 清空mySet.clear();console.log(mySet.size);// 0🔄 遍历方法
Set默认是可迭代的,可以使用for...of循环,也提供了以下遍历方法:
keys(): 返回键名的遍历器(对于 Set,键名即值)values(): 返回键值的遍历器entries(): 返回键值对的遍历器forEach(): 使用回调函数遍历每个成员
constset=newSet(["red","green","blue"]);for(letitemofset){console.log(item);}// red// green// blue// 转换为数组进行高阶操作constarr=[...set];// 或者constarr2=Array.from(set);3. ⚔️ 实战场景:去重与集合运算
这是Set最发光发热的地方。
✅ 场景一:数组去重(最经典用法)
以前我们需要用indexOf或filter配合对象哈希来去重,现在一行代码搞定。
constarr=[1,2,2,3,4,4,5];// 方法:Set -> ArrayconstuniqueArr=[...newSet(arr)];// 或者constuniqueArr2=Array.from(newSet(arr));console.log(uniqueArr);// [1, 2, 3, 4, 5]注意:这种方法只能去除基本类型的重复值。如果数组中包含对象
{},由于引用地址不同,无法去重。
✅ 场景二:集合运算(交、并、差)
利用Set和数组方法,可以轻松实现数学中的集合运算。
consta=newSet([1,2,3]);constb=newSet([2,3,4]);// 1. 并集 (Union): a + bconstunion=newSet([...a,...b]);console.log(union);// Set(4) { 1, 2, 3, 4 }// 2. 交集 (Intersection): a ∩ b (既在 a 又在 b)constintersection=newSet([...a].filter((x)=>b.has(x)));console.log(intersection);// Set(2) { 2, 3 }// 3. 差集 (Difference): a - b (在 a 但不在 b)constdifference=newSet([...a].filter((x)=>!b.has(x)));console.log(difference);// Set(1) { 1 }✅ 场景三:高性能查找
当数据量很大时,Array.includes()的时间复杂度是O(N),而Set.has()的时间复杂度接近O(1)。
constlargeArray=Array.from({length:100000},(_,i)=>i);constlargeSet=newSet(largeArray);console.time("Array Search");largeArray.includes(99999);console.timeEnd("Array Search");// 较慢console.time("Set Search");largeSet.has(99999);console.timeEnd("Set Search");// 极快4. 💡 进阶技巧:WeakSet 与性能分析
📉 WeakSet:垃圾回收的好帮手
WeakSet结构与Set类似,也是不重复值的集合。但它有两个显著区别:
- 成员只能是对象,不能是基本类型值。
- 弱引用:其中的对象如果没有其他引用,会被垃圾回收机制自动回收,不会导致内存泄漏。
适用场景:存储 DOM 节点标记、缓存对象引用等临时性数据。
constws=newWeakSet();constobj={};ws.add(obj);console.log(ws.has(obj));// trueobj=null;// 解除引用// 此时,ws 中的 obj 可能会被垃圾回收,具体取决于引擎实现注意:
WeakSet不可遍历,没有size属性,也没有keys/values/entries方法。
🚀 性能对比总结
| 操作 | Array | Set | 优势方 |
|---|---|---|---|
| 查找元素 | O(N) (线性扫描) | O(1) (哈希表) | Set(数据越大优势越明显) |
| 添加元素 | O(1) (末尾) | O(1) | 平手 |
| 删除元素 | O(N) (需查找+移动) | O(1) | Set |
| 去重 | 需额外逻辑 | 天然支持 | Set |
| 内存占用 | 较低 | 略高 (哈希表开销) | Array |
5. ❌ 常见误区与坑点
1. 引用类型无法自动去重
consts=newSet();s.add({id:1});s.add({id:1});console.log(s.size);// 2!因为两个对象内存地址不同解决方案:如果需要基于对象的某个属性去重,需先处理数据(如转为 JSON 字符串或使用 Map)。
2. +0 和 -0
在Set中,+0和-0被视为相同的值。
consts=newSet();s.add(+0);s.add(-0);console.log(s.size);// 13. Set 转数组的顺序
Set保持插入顺序。遍历时,先添加的元素先被遍历。这一点与某些语言中的 HashSet(无序)不同,请务必记住。
6. 💡 总结与选型建议
📝 核心总结
| 特性 | Array | Set |
|---|---|---|
| 有序性 | ✅ 有序 | ✅ 有序 (插入顺序) |
| 唯一性 | ❌ 可重复 | ✅ 唯一 |
| 查找速度 | 慢 (O(N)) | 快 (O(1)) |
| 数据类型 | 任意 | 任意 (WeakSet 仅限对象) |
| 主要用途 | 列表、栈、队列 | 去重、快速查找、集合运算 |
🚀 博主寄语
- 日常开发:遇到“去重”需求,无脑选
Set。[...new Set(arr)]是最优雅的写法。 - 性能敏感:如果需要频繁判断“某元素是否存在”,请将数据存入
Set而不是Array。 - DOM 操作:如果需要标记一组 DOM 元素且不担心内存泄漏问题,可以考虑
WeakSet。 - 面试加分项:提到
Set基于哈希表实现,所以查找效率高;提到NaN的特殊处理;提到WeakSet的垃圾回收机制。
记住口诀:
数组去重用 Set,一行代码解千愁。
查找频繁建集合,哈希原理速度快。
对象引用要注意,地址不同算两个。
弱引用里存节点,内存泄漏不用忧。
交集并集差集算,Filter Has 组合秀。
希望这篇文档能帮你彻底掌握Set的用法!如果有疑问,欢迎在评论区留言。👇
喜欢这篇文章吗?记得点赞、收藏、转发哦!❤️
