基于JavaScript Set实现交集、并集和差集运算代码
详细讲解在JavaScript中基于原生Set对象实现并集、交集、差集与对称差集的实现原理、代码示例、ES2024规范新方法以及在实际开发中的性能优化与注意事项。
在日常前端与 Node.js 业务开发中,数据去重、用户标签匹配、权限交叉判定以及状态列表差分等需求十分常见。JavaScript 在 ES6(ECMAScript 2015)中引入了原生的 Set 数据结构,它能够自动去重并提供平均复杂度为 O(1) 的元素查找能力。本文将完整介绍如何基于 Set 及其扩展机制,实现数学集合论中的交集(Intersection)、并集(Union)、差集(Difference)和对称差集(Symmetric Difference),并说明 ES2024 原生集合方法的最新演进与性能优化考量。
一、JavaScript Set 的特性与基础准备
在 ES6 规范中,Set 是一组不包含重复值的键值集合。它的主要方法和属性如下:
add(value):向集合添加元素,若已存在则忽略。has(value):检测元素是否存在于集合中,返回布尔值。此操作的平均时间复杂度为 O(1)。delete(value):从集合中移除指定元素。size:返回当前集合的元素个数。
因为原生的 Array.prototype.filter 和扩展运算符(Spread Operator)能够便捷地在数组与可迭代对象之间桥接,在过去很长一段时间内,开发者最常用的集合运算方案是:将 Set 转换为数组,通过 filter 结合 has 过滤,最终再构造为新的 Set。
二、传统集合运算代码实现与操作流程
下面给出最经典的通用函数实现。这些函数既适用于现代浏览器环境,也适用于各类基于 Node.js 的旧版与通用运行时。
1. 并集(Union)
并集表示包含属于集合 A 或集合 B 的所有元素。通过扩展运算符将两个 Set 展开至同一个数组中并传给 new Set(),即可利用其内部哈希机制完成去重:
function union(setA, setB) {
return new Set([...setA, ...setB]);
}
// 示例
const a = new Set([1, 2, 3]);
const b = new Set([3, 4, 5]);
console.log(union(a, b)); // Set(5) { 1, 2, 3, 4, 5 }
2. 交集(Intersection)
交集表示同时存在于集合 A 和集合 B 中的元素集合。通过筛选 setA 中在 setB 也存在的项:
function intersection(setA, setB) {
// 性能优化:遍历较小的集合,检查是否存在于较大的集合中
const [smaller, larger] = setA.size < setB.size ? [setA, setB] : [setB, setA];
return new Set([...smaller].filter(item => larger.has(item)));
}
// 示例
const a = new Set([1, 2, 3]);
const b = new Set([2, 3, 4]);
console.log(intersection(a, b)); // Set(2) { 2, 3 }
3. 差集(Difference)
差集(相对补集)表示属于集合 A 但不属于集合 B 的元素(记作 A \ B)。需要注意顺序敏感性:A \ B 与 B \ A 的结果通常不同。
function difference(setA, setB) {
return new Set([...setA].filter(item => !setB.has(item)));
}
// 示例
const a = new Set([1, 2, 3, 4]);
const b = new Set([3, 4, 5]);
console.log(difference(a, b)); // Set(2) { 1, 2 }
console.log(difference(b, a)); // Set(1) { 5 }
4. 对称差集(Symmetric Difference)
对称差集是指属于集合 A 或属于集合 B,但不同时属于两者的元素组合。它等价于 (A \ B) ∪ (B \ A),也可通过“从并集中移除交集”实现:
function symmetricDifference(setA, setB) {
const diffA = [...setA].filter(x => !setB.has(x));
const diffB = [...setB].filter(x => !setA.has(x));
return new Set([...diffA, ...diffB]);
}
// 示例
const a = new Set([1, 2, 3]);
const b = new Set([2, 3, 4]);
console.log(symmetricDifference(a, b)); // Set(2) { 1, 4 }
三、ES2024 原生集合方法对比与演变
ECMAScript 2024 正式为 Set.prototype 增加了原生集合运算方法(TC39 Set Methods Proposal)。在 Node.js 22+、Chrome 122+、Safari 17+ 等现代环境中,已无需手动书写工具函数即可直接调用原生底层方法。
原生 API 调用示例如下:
const setA = new Set(['admin', 'editor', 'viewer']);
const setB = new Set(['viewer', 'auditor']);
// 原生交集
const commonRoles = setA.intersection(setB);
console.log(commonRoles); // Set(1) { 'viewer' }
// 原生并集
const allRoles = setA.union(setB);
console.log(allRoles); // Set(4) { 'admin', 'editor', 'viewer', 'auditor' }
// 原生差集
const exclusiveToA = setA.difference(setB);
console.log(exclusiveToA); // Set(2) { 'admin', 'editor' }
四、关键细节与实战陷阱
Set 判断元素相等的标准是基于 SameValueZero 算法。这意味着基本类型(数字、字符串、布尔值)能正确匹配,且 NaN 与 NaN 视为相等;但针对对象、数组、函数等引用类型,只有指向同一内存地址时才视为相同元素。
例如以下代码中,两个内容完全相同的对象字面量在 Set 中不会被判定为相等:
const a = new Set([{ id: 1 }, { id: 2 }]);
const b = new Set([{ id: 1 }, { id: 3 }]);
// 结果依然包含两个 { id: 1 } 对象,因为内存引用不同
console.log(new Set([...a, ...b]).size); // 4
如果业务数据是对象数组,应当在运算前将对象提取为具有唯一标识的主键集合(例如 id 或 uuid),或者结合 Map 按照特定键值进行哈希归约运算。
五、多场景选择与全景总结
在工程实践中,选用哪种集合运算策略取决于代码运行的宿主环境与数据集体量:
- 现代 Node.js 与高版本浏览器环境:优先采用原生 ES2024 原生方法(如
setA.intersection(setB)),代码可读性最高且避免中间临时数组的内存垃圾回收(GC)开销。 - 兼容旧宿主环境:使用展开运算符与
filter结合的工具函数封装。交集操作务必加上“较小集合优先”判断,防止大集合无谓迭代。 - 大数据量场景(超过 10 万级):避免直接展开为巨型数组(可能触碰参数个数限制或内存突增),推荐使用
for...of直接在循环中调用目标集合的add或delete。
JS动态构建复杂嵌套JSON对象的方法
2026-11-05 15:41:52
JS负数取余为什么是负数?详解余数与模运算区别
2026-11-05 13:34:41
JS 解压出现乱码或报错怎么解决?
2026-11-05 12:32:38
JS 中 startsWith、indexOf 与正则判断字符串前缀的对比
2026-11-04 22:19:43
JS解析Excel后怎么将数据转换为JSON格式
2026-11-04 20:58:28








