数据的“洁癖”管家:深入解析 JavaScript Set

🧊 数据的"洁癖"管家:深入解析 JavaScript Set

🤔 为什么我们需要 Set?

在开发中,我们经常遇到这样的场景:

  1. 数组去重:从后端获取了一堆标签,需要去除重复项。
  2. 快速查找:判断某个用户 ID 是否已经在黑名单中。
  3. 集合运算:求两个用户群体的交集、并集或差集。

如果使用传统的 Array,这些操作往往需要嵌套循环或复杂的逻辑,代码冗长且性能低下(尤其是数据量大时)。而 Set 天生就是为了解决"唯一性"和"高效查找"而生的。

通俗比喻

  • Array(数组) :像是一个普通的储物箱。你可以往里面扔任何东西,哪怕是一模一样的苹果,它也会照单全收。如果你想找某个苹果,你得把箱子倒出来一个个看。
  • Set(集合) :像是一个带有自动识别功能的智能货架
    • 唯一性:如果你试图放入一个已经存在的苹果,货架会直接拒绝:"这个已经有了!"
    • 高效性:货架内部有索引,问你"有没有红富士?"它能瞬间回答"有"或"没有",不需要逐个翻找。

📂 目录

  1. [🔍 核心概念与基本用法](#🔍 核心概念与基本用法)
  2. [🛠️ 常用 API 详解](#🛠️ 常用 API 详解)
  3. [⚔️ 实战场景:去重与集合运算](#⚔️ 实战场景:去重与集合运算)
  4. [💡 进阶技巧:WeakSet 与性能分析](#💡 进阶技巧:WeakSet 与性能分析)
  5. [❌ 常见误区与坑点](#❌ 常见误区与坑点)
  6. [💡 总结与选型建议](#💡 总结与选型建议)

1. 🔍 核心概念与基本用法

Set 本身是一个构造函数,用来生成 Set 数据结构。

✅ 基本创建

javascript 复制代码
// 1. 创建一个空的 Set
const s1 = new Set();

// 2. 通过数组初始化(自动去重)
const s2 = new Set([1, 2, 3, 2, 1]);
console.log(s2); // Set(3) { 1, 2, 3 }

// 3. 通过字符串初始化(字符串也是可迭代对象)
const s3 = new Set("hello");
console.log(s3); // Set(4) { 'h', 'e', 'l', 'o' } -> 注意 'l' 只保留了一个

⚠️ 判断相等的规则

Set 内部判断两个值是否相等,使用的是 "Same-value-zero" 算法。

  • 它类似于严格相等运算符 (===)。
  • 主要区别 :在 Set 中,NaN 等于 NaN
javascript 复制代码
const s = new Set();
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

💻 代码示例

javascript 复制代码
const mySet = new Set();

// 添加元素
mySet.add(1).add(2).add(2); // 链式调用,第二个 2 被忽略
console.log(mySet.size); // 2

// 判断存在
console.log(mySet.has(1)); // true
console.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(): 使用回调函数遍历每个成员
javascript 复制代码
const set = new Set(["red", "green", "blue"]);

for (let item of set) {
  console.log(item);
}
// red
// green
// blue

// 转换为数组进行高阶操作
const arr = [...set];
// 或者
const arr2 = Array.from(set);

3. ⚔️ 实战场景:去重与集合运算

这是 Set 最发光发热的地方。

✅ 场景一:数组去重(最经典用法)

以前我们需要用 indexOffilter 配合对象哈希来去重,现在一行代码搞定。

javascript 复制代码
const arr = [1, 2, 2, 3, 4, 4, 5];

// 方法:Set -> Array
const uniqueArr = [...new Set(arr)];
// 或者
const uniqueArr2 = Array.from(new Set(arr));

console.log(uniqueArr); // [1, 2, 3, 4, 5]

注意 :这种方法只能去除基本类型 的重复值。如果数组中包含对象 {},由于引用地址不同,无法去重。

✅ 场景二:集合运算(交、并、差)

利用 Set 和数组方法,可以轻松实现数学中的集合运算。

javascript 复制代码
const a = new Set([1, 2, 3]);
const b = new Set([2, 3, 4]);

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

// 2. 交集 (Intersection): a ∩ b (既在 a 又在 b)
const intersection = new Set([...a].filter((x) => b.has(x)));
console.log(intersection); // Set(2) { 2, 3 }

// 3. 差集 (Difference): a - b (在 a 但不在 b)
const difference = new Set([...a].filter((x) => !b.has(x)));
console.log(difference); // Set(1) { 1 }

✅ 场景三:高性能查找

当数据量很大时,Array.includes() 的时间复杂度是 O(N) ,而 Set.has() 的时间复杂度接近 O(1)

javascript 复制代码
const largeArray = Array.from({ length: 100000 }, (_, i) => i);
const largeSet = new Set(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 类似,也是不重复值的集合。但它有两个显著区别:

  1. 成员只能是对象,不能是基本类型值。
  2. 弱引用:其中的对象如果没有其他引用,会被垃圾回收机制自动回收,不会导致内存泄漏。

适用场景:存储 DOM 节点标记、缓存对象引用等临时性数据。

javascript 复制代码
const ws = new WeakSet();
const obj = {};

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

obj = 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. 引用类型无法自动去重

javascript 复制代码
const s = new Set();
s.add({ id: 1 });
s.add({ id: 1 });
console.log(s.size); // 2!因为两个对象内存地址不同

解决方案:如果需要基于对象的某个属性去重,需先处理数据(如转为 JSON 字符串或使用 Map)。

2. +0 和 -0

Set 中,+0-0 被视为相同的值。

javascript 复制代码
const s = new Set();
s.add(+0);
s.add(-0);
console.log(s.size); // 1

3. 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 的用法!如果有疑问,欢迎在评论区留言。👇

喜欢这篇文章吗?记得点赞、收藏、转发哦! ❤️

相关推荐
码界筑梦坊12 小时前
144-基于Flask的电商超市数据可视化分析系统
开发语言·python·信息可视化·数据分析·flask
之歆12 小时前
Day16_JavaScript Event 对象深度解析(上篇)
开发语言·javascript·ecmascript
聆风吟º12 小时前
深入理解C语言 islower 函数详解:判断字符是否为小写字母
c语言·开发语言·库函数·字符处理·islower
Zhang~Ling12 小时前
C++继承机制详解上:概念、语法、作用域与转换规则
开发语言·c++
wengqidaifeng12 小时前
C++从菜鸟到强手:2.类和对象(中)—— 拷贝、赋值与运算符重载
开发语言·c++
0x000712 小时前
Git Bash 中无法启动 Claude Code ?
开发语言·git·bash
冴羽yayujs12 小时前
前端周报:Google I/O 发布 Agentic Web、TypeScript 6.0 正式版、npm 安全新策略
前端·javascript·前端开发·前端学习·前端周报
彦为君12 小时前
Spring定时任务开发指南(动态实现)
java·开发语言·后端·python·spring·wpf
不绝19112 小时前
AB包相关知识
开发语言·lua