JavaScript Set基础用法:创建、增删改查与遍历代码实例

作者:木棉

时间:2026-11-02

浏览:139

深入解析JavaScript ES6 Set集合数据结构的基础用法:从创建初始化、增删改查(CRUD)操作,到for...of与迭代器遍历,结合数组去重与集合运算实战代码实例与避坑指南。

文章目录
JavaScript Set基础用法:创建、增删改查与遍历代码实例
图 0:JavaScript Set 数据结构核心全景预览
在前端与 Node.js 业务开发中,我们常常需要维护一组“不允许重复”的数据集合,例如用户选中的多项标签 ID、已授予的权限编码或网络请求的去重队列。如果继续使用普通数组配合 includes() 或 indexOf() 手动查重,不仅逻辑琐碎,其线性扫描在数据量增长时还会带来 $O(N)$ 的性能损耗。ES6 引入的原生 Set 数据结构提供了天然的唯一性保证与 $O(1)$ 的高速检索能力。本文将从创建初始化、增删改查(CRUD)到遍历与集合运算,通过具体代码与执行示例完整讲解 Set 的实用用法。

一、认识 Set:核心特性与底层判定机制

Set 是 JavaScript ES6(ECMAScript 2015)正式推出的内置集合对象。它类似于数组,但具有两个最根本的差异:

  • 元素唯一性:集合内部不允许存在两个完全相同的值,任何重复添加操作都会被自动且静默地忽略。
  • 保持插入时序:尽管 Set 基于哈希结构实现高效检索,但它在规范层面保证了严格按照元素的插入顺序进行迭代。

很多开发者容易混淆 Set 判定“两值是否重复”的规则。Set 内部采用的是 ECMAScript 规范中的 SameValueZero 算法。该算法与全等比对运算符 === 非常接近,主要有两点关键区别:

  1. NaN 的处理:在 === 运算中,NaN === NaN 为 false;而在 Set 内部,NaN 被视为相互相等,多次添加 NaN 只会保留一份。
  2. 符号零的处理:正零 +0 与负零 -0 在 Set 中被判定为完全相同,不会重复存储。
注意对象引用的陷阱:SameValueZero 对引用数据类型(对象、数组、函数)仅比对内存引用地址。即便两个字面量对象内容完全一致(例如 { id: 1 }),它们在堆内存中的地址不同,Set 会将它们作为两个独立元素保存。
JavaScript Set基础用法:创建、增删改查与遍历代码实例
图 1:Set 元素输入、SameValueZero 判断机制与有序底层存储模型

二、创建 Set 实例(初始化)

创建 Set 实例统一使用 new Set() 构造函数。你可以创建一个初始为空的集合,也可以传入任何遵循可迭代协议(Iterable)的数据结构(如 Array、String、NodeList 等)完成快速填充与去重。

1. 创建空集合

const emptySet = new Set();
console.log(emptySet.size); // 示例输出: 0

2. 使用数组或其他可迭代对象初始化

当传入包含重复项的数组时,构造函数会在初始构建时一次性完成去重:

// 传入带有重复项的数组
const numberSet = new Set([10, 20, 20, 30, 10]);
console.log(numberSet);      // 示例输出: Set(3) { 10, 20, 30 }
console.log(numberSet.size); // 示例输出: 3

// 传入字符串(字符串本身也是可迭代对象,自动拆解字符并去重)
const charSet = new Set('javascript');
console.log(charSet);        // 示例输出: Set(9) { 'j', 'a', 'v', 's', 'c', 'r', 'i', 'p', 't' }
区别提醒:读取数组长度使用 arr.length 属性,而读取 Set 中元素数量必须使用 set.size 属性。Set 没有 length 属性,直接访问会返回 undefined。

三、Set 的增删改查(CRUD)操作

Set 提供了一组语义极其清晰的原生操作 API。需要特别理解的是:由于 Set 是“无键、无索引”的值集合,其增删查与常规数组存在本质差异。

JavaScript Set基础用法:创建、增删改查与遍历代码实例
图 2:JavaScript Set 增、删、改、查核心 API 与方法全景交互

1. 增(Create / Add):add() 方法

调用 set.add(value) 向集合末尾追加一个新元素。如果该元素已存在,则不会发生任何改动。该方法返回当前 Set 实例本身,因此支持连贯的链式调用。

const skills = new Set();

// 基础单次添加
skills.add('HTML');
skills.add('CSS');

// 链式调用
skills.add('JavaScript').add('TypeScript');

// 重复添加同一元素,不会产生冗余
skills.add('HTML');

console.log(skills.size); // 示例输出: 4
console.log(skills);      // 示例输出: Set(4) { 'HTML', 'CSS', 'JavaScript', 'TypeScript' }

2. 删(Delete):delete() 与 clear() 方法

Set 提供了单项删除与一键清空两种删除操作:

  • set.delete(value):从集合中移除指定元素。若该元素原本存在且删除成功,返回 true;若元素不存在,返回 false。
  • set.clear():清除集合中的所有成员,无返回值(undefined),调用后 size 变为 0。
const statusCodes = new Set([200, 404, 500]);

// 成功删除存在的值
const result1 = statusCodes.delete(404);
console.log(result1);          // 示例输出: true
console.log(statusCodes.size); // 示例输出: 2

// 尝试删除不存在的值
const result2 = statusCodes.delete(999);
console.log(result2);          // 示例输出: false

// 一键清空整个集合
statusCodes.clear();
console.log(statusCodes.size); // 示例输出: 0

3. 查(Read / Query):has() 方法

set.has(value) 用于检测集合中是否包含某个特定元素,返回布尔值(true 或 false)。

这是 Set 相对于 Array 的核心性能优势所在:数组的 arr.includes(val) 必须从头到尾进行线性对比,时间复杂度为 $O(N)$;而 Set 的底层基于哈希表寻址,set.has(val) 的平均时间复杂度仅为 $O(1)$,在大数据量判断时速度提升尤为明显。

const rolePermissions = new Set(['user:read', 'user:write', 'order:pay']);

console.log(rolePermissions.has('user:read'));   // 示例输出: true
console.log(rolePermissions.has('admin:manage')); // 示例输出: false

// 注意:Set 无法通过数值下标随机访问元素
console.log(rolePermissions[0]); // 示例输出: undefined

4. 改(Update):更新与替换策略

由于 Set 不具备类似数组的索引(如 arr[0] = newVal)或对象的键映射(如 map.set(key, val)),因此原生并没有提供名为 update() 的 API。修改现有元素通常分为两种业务场景:

  • 场景 A:修改基础原始类型(替换值)
    推荐模式是“先 delete 旧值,再 add 新值”。这样既能保证原值彻底被注销,又能让新值通过 SameValueZero 检查。
  • 场景 B:修改引用对象内的属性
    若 Set 中存放的是引用对象,直接修改对象内部的属性并不会改变对象本身的内存指针引用,该对象在 Set 中的存储依然有效。
// 场景 A:替换原始值(例如重命名分类)
const categories = new Set(['Tech', 'Life', 'Finance']);
function replaceSetValue(targetSet, oldValue, newValue) {
  if (targetSet.has(oldValue)) {
    targetSet.delete(oldValue);
    targetSet.add(newValue);
    return true;
  }
  return false;
}
replaceSetValue(categories, 'Tech', 'Science & Tech');
console.log(categories); // 示例输出: Set(3) { 'Life', 'Finance', 'Science & Tech' }

// 场景 B:修改引用对象的属性
const userSession = { id: 101, active: false };
const activeUsers = new Set([userSession]);

// 直接修改对象内部属性,Set 保持对其引用的跟踪
userSession.active = true;
console.log([...activeUsers][0].active); // 示例输出: true

四、Set 的遍历与迭代实战

Set 原生实现了可迭代协议(Iterable),因此能够与现代 JavaScript 的各种迭代语法无缝协作。所有遍历方式都严格按照元素的插入时序逐个产出。

JavaScript Set基础用法:创建、增删改查与遍历代码实例
图 3:Set 遍历方式、迭代器流转与数据消费链路

1. for...of 循环(首选语法)

for...of 是遍历 Set 最直观、最符合现代工程规范的写法。它支持在循环体内使用 break、continue 或 return 提早中断控制流。

const frameworks = new Set(['Vue', 'React', 'Svelte']);

for (const framework of frameworks) {
  console.log('框架名称:', framework);
}
/* 示例输出:
框架名称: Vue
框架名称: React
框架名称: Svelte
*/

2. forEach() 回调遍历

Set 也提供了与 Array 类似的 forEach((value, key, set) => ...) 方法。

为什么 key 与 value 完全相同?由于 Set 只有值没有键,ECMAScript 为了与 Map.prototype.forEach 的参数签名保持完全统一,规定回调函数中的前两个参数 value 和 key 接收相同的值。
const tags = new Set(['frontend', 'backend']);

tags.forEach((value, key, ownerSet) => {
  console.log(`value: ${value}, key: ${key}, 恒等: ${value === key}`);
});
/* 示例输出:
value: frontend, key: frontend, 恒等: true
value: backend, key: backend, 恒定: true
*/

3. keys()、values() 与 entries() 迭代器

Set 实现了这三个返回迭代器(Iterator)的标准方法:

  • set.values():返回包含集合所有元素的迭代器。这是 Set 的默认迭代器(即 set[Symbol.iterator] 的等效方法)。
  • set.keys():为了与 Map 接口对齐而提供,其行为与 set.values() 完全一致。
  • set.entries():返回键值对迭代器,产出形式为 [value, value] 的二元数组。
const colors = new Set(['red', 'blue']);

for (const [key, val] of colors.entries()) {
  console.log(`[${key}, ${val}]`);
}
/* 示例输出:
[red, red]
[blue, blue]
*/

4. 展开语法(...)与 Array.from() 转回数组

由于 Set 本身没有提供 map()、filter() 或 reduce() 等数组高阶操作,我们在实际业务中经常使用展开运算符(Spread Syntax)或 Array.from() 快速将 Set 转回 Array 进行复杂加工:

const scores = new Set([60, 85, 95, 60]);

// 展开为数组并使用 map 处理
const doubled = [...scores].map(n => n * 2);
console.log(doubled); // 示例输出: [ 120, 170, 190 ]

五、实战应用:数组去重与经典集合运算

在日常开发中,利用 Set 实现集合运算不仅代码简明,执行效率也显著优于多次嵌套循环。

JavaScript Set基础用法:创建、增删改查与遍历代码实例
图 4:常用集合运算模型(并集 / 交集 / 差集)与 Array 特性对比总结

1. 经典一行代码实现数组去重

const rawUserIds = [1001, 1002, 1001, 1003, 1002];

// 单行完成基础数据类型去重
const uniqueIds = [...new Set(rawUserIds)];
console.log(uniqueIds); // 示例输出: [ 1001, 1002, 1003 ]

2. 并集、交集与差集的实现

假如有集合 A 和集合 B,我们可以通过结合扩展运算符和 set.has() 快速完成数学集合运算:

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

// 并集 (Union):合并两集合的所有不重复成员
const union = new Set([...setA, ...setB]);
console.log([...union]); // 示例输出: [ 1, 2, 3, 4, 5, 6 ]

// 交集 (Intersection):仅保留同时存在于 A 和 B 的成员
const intersection = new Set([...setA].filter(x => setB.has(x)));
console.log([...intersection]); // 示例输出: [ 3, 4 ]

// 差集 (Difference):保留存在于 A 但不存在于 B 的成员 (A \ B)
const difference = new Set([...setA].filter(x => !setB.has(x)));
console.log([...difference]); // 示例输出: [ 1, 2 ]
新标准前瞻(Set Methods): 在 ECMAScript 较新规范(ES2025)中,JavaScript 已逐步为 Set.prototype 新增了原生集合运算方法,包括 setA.union(setB)、setA.intersection(setB) 及 setA.difference(setB) 等。在现代浏览器或新版 Node.js 中可直接使用原生方法,而上述基于 filter 和 has 的写法依然是全平台通用的最佳兼容方案。

六、常见开发误区与避坑指南

误区场景 典型错误代码 正确应对方式
对象字面量无法自动去重 new Set([{ id: 1 }, { id: 1 }]).size === 2 对象按内存地址比对,字面量引用不同。对象数组去重建议先序列化键(如 Map 按主键 ID 去重)。
误用 length 属性获取大小 const count = mySet.length; // undefined 必须使用 mySet.size 获取集合元素数量。
直接调用数组专属方法 mySet.map(x => x * 2); // TypeError: not a function 先使用展开语法转为数组 [...mySet].map(...),或使用 for...of 循环迭代。
试图通过数字下标取值 const first = mySet[0]; // undefined 使用解构 const [first] = mySet; 或 mySet.values().next().value。

综上所述,JavaScript Set 不仅是实现高效数组去重的利器,更是处理无序排他元素、权限校验以及复杂集合运算的核心底层数据结构。熟练掌握其增删查 API 与遍历方式,能显著提高代码的执行效率与逻辑清晰度。

实用工具推荐

电子包浆

绿化、压缩、噪点和虚拟水印,自由调节做旧程度。

使用 ›
图片压缩

选择画质和输出格式,预览压缩前后的大小。

使用 ›
相关文章