以前想对两个 Set 做交集、并集、差集,你都得自己写循环或者转数组再 filter。虽然网上有各种一行代码的写法,但总归是“野路子”。这次 ES2025(实际上是 ES2024 年的提案,但今时今日已逐步落地)直接给 Set 加了一票原生方法,让我终于能甩掉工具函数了。
今天用实际场景好好聊聊 union()、intersection()、difference()、symmetricDifference() 和几个判断方法。放心,每一个都有代码例子,直接用。
先看一眼新方法全家福
新的 Set 方法分为两类:返回新 Set 的,以及返回布尔值的。
union(other)—— 并集,返回包含两个集合所有元素的新 Setintersection(other)—— 交集,返回同时存在于两个集合的元素difference(other)—— 差集,返回存在于当前集合但不在 other 中的元素symmetricDifference(other)—— 对称差集,返回只存在于其中一个集合的元素(相当于全集减去交集)isSubsetOf(other)—— 是否是 another 的子集isSupersetOf(other)—— 是否包含 another 的全部元素isDisjointFrom(other)—— 是否和 another 没有交集
还有一个额外的方法 toSorted() ?那是数组的,不是本文重点。咱们就盘 Set 的这些。
快速上手:一个权限系统的例子
假设你有一个用户权限列表,用一个 Set 存储。现在有个操作需要某些权限,你却要判断用户是否有交集?过去可能写一堆,现在直接一行。
看下面这段代码,不用任何 polyfill,只是把每个方法串起来。
// 用户当前拥有的权限
const userPerms = new Set(['read', 'write', 'share', 'export']);
// 执行某个高危操作需要的权限
const requiredPerms = new Set(['read', 'delete', 'admin']);
// 1. 检查用户是否有任一高危权限(交集非空)
const canDoHighRisk = !userPerms.intersection(requiredPerms).size;
console.log(canDoHighRisk); // false,因为 read 是交集,所以结果为 false
// 2. 检查用户是否满足所有权限(是 requiredPerms 的超集)
const isAdmin = userPerms.isSupersetOf(requiredPerms);
console.log(isAdmin); // false
// 3. 用户还没有的权限(差集)
const missingPerms = requiredPerms.difference(userPerms);
console.log([...missingPerms]); // ["delete", "admin"]
// 4. 把两个权限集合合并成一个更大的集合(并集)
const allPerms = userPerms.union(requiredPerms);
console.log([...allPerms]); // ["read", "write", "share", "export", "delete", "admin"]
// 5. 对称差集:看看双方有哪些不同的权限
const diffPerms = userPerms.symmetricDifference(requiredPerms);
console.log([...diffPerms]); // ["write", "share", "export", "delete", "admin"]
直接用浏览器最新的 Chrome 或 Node 22+ 能跑。如果你想在旧环境用,建议装一个 core-js 的对应 polyfill,但这里咱们只聊原生。
实战:筛选出没有重叠的用户标签
你有一个用户 ID 列表,另一个是“已经下发优惠券”的用户 ID 列表。你想知道哪些用户从未收到过券,也想知道哪些用户既领了券又可能参加活动。这类逻辑过去要写一堆循环,现在就是 Set 的一顿饭。
// 注册用户 ID
const registered = new Set(['001', '002', '003', '004']);
// 已发过优惠券的用户 ID
const couponed = new Set(['003', '004', '005', '006']);
// 从未收到过券的用户(注册用户 - 已发券用户)
const noCouponUsers = registered.difference(couponed);
console.log('从未收券:', [...noCouponUsers]); // ["001", "002"]
// 既注册又领了券的用户(交集)
const both = registered.intersection(couponed);
console.log('重叠人群:', [...both]); // ["003", "004"]
// 两个群体中所有不重复的人(并集)
const allUsers = registered.union(couponed);
console.log('全部人数:', [...allUsers]); // ["001", "002", "003", "004", "005", "006"]
// 只注册未领券 + 只领券未注册(对称差)
const onlyOneSide = registered.symmetricDifference(couponed);
console.log('只占一头:', [...onlyOneSide]); // ["001", "002", "005", "006"]
一个很大的改进点:这些方法返回的是 新 Set,不会修改原来的 Set。也就是说你不用担心误改数据。
几个容易忽略的细节
这些新方法接收的参数可以是 Set,也可以是任何“类 Set 对象”(比如数组?不,文档标准是要求一个具有 size 和 keys() 或者迭代器的对象)。但我测试中传数组会报错,所以还是老老实实传 Set。不过某些引擎里提供了更宽松的行为,最好传 Set。
值比较遵循 SameValueZero 规则,也就是说 NaN 和 NaN 是相等的,0 和 -0 也是相等的。这对 Set 本来就默认,所以很自然。
还有一个特点:这些方法都讲究“主次顺序”。比如 union() 返回的 Set 会先包含调用者里的元素,再包含传参里的元素;difference() 自然是以调用者为主。如果你关心顺序,可以把主体放在左边。
性能?别担心了
原生方法大概率比你手写的 for 循环要快,因为引擎可以直接用内部数据结构操作,避免迭代器开销。而且它们会提前计算大小并且自动跳过重复添加。比如 union 本质上就是把两个哈希表合并,引擎如果检测到两个 Set 的底层哈希表同构,有机会直接做批量操作。这些性能收益只有在超大集合上才能感觉出来,但至少不会成为短板。
一个真实的业务场景:过滤周末和休息日
假设你有一个“可预约日期”的 Set,还有一个“节假日”的 Set,需要算出真正可预约的工作日。这样写是不是很漂亮?
const availableDates = new Set([
'2025-06-02',
'2025-06-03',
'2025-06-04',
'2025-06-05',
'2025-06-06',
'2025-06-07',
'2025-06-08'
]);
const holidays = new Set([
'2025-06-04',
'2025-06-05'
]);
const workDays = availableDates.difference(holidays);
console.log('可预约工作日:', [...workDays]);
// ["2025-06-02", "2025-06-03", "2025-06-06", "2025-06-07", "2025-06-08"]
注意这里周六日也算可预约,因为咱们没排除周末,反正就是说这方法很清晰。
isSubsetOf / isSupersetOf 的判断妙用
在做权限校验时,经常需要判断“请求要求的权限是否属于用户权限集合的子集”,直接用 required.isSubsetOf(userPerms) 就完美了。
function canAccess(userPerms, requiredPerms) {
return requiredPerms.isSubsetOf(userPerms);
}
const role1 = new Set(['read', 'write']);
const role2 = new Set(['read', 'write', 'execute']);
console.log(canAccess(role1, role2)); // false,因为 role2 不是 role1 的子集
console.log(canAccess(role2, role1)); // true
什么时候别用这些方法?
如果你是处理基本类型(字符串、数字),那没问题。但如果你的 Set 里存的是对象,而且你希望按照引用比较,那也 OK。但是如果你需要深比较,那还得自己用循环。另外在旧版本浏览器中,你可能需要这种 fallback 写法,但这不是今天主题。
有人会问,这些方法能用在 Node 的 Set 上吗?可以,只要 Node 22.3.0 以上(大概),Node 18 和 20 可能没得,建议直接用最新版。
一行代码兼容旧环境?
因为新方法还没有完全覆盖所有运行时,实际开发中你又想用,可以手动扩展 Set 原型,但不推荐。我更推荐你使用 core-js/actual/set/union 等按需引入 polyfill,或者干脆用 Babel 编译。但是自己写个简易的也不难,比如:
if (!Set.prototype.union) {
Set.prototype.union = function(other) {
return new Set([...this, ...other]);
};
}
但这种简易 polyfill 性能略慢,而且很多方法容易写错,比如 difference 要避免重复。所以还是等标准普及吧。
最终总结
ES2025 给 Set 添加的这些方法,不是花架子,是从 Python、Ruby 这些语言里吸取了经验,真正补充了 JavaScript 集合操作的短板。以后遇到“求交集”“校验包含”这种需求,咱们就不用再满脑子转 filter 和 includes 了。你看,我写权限的时候,思维都跟着简洁了很多。
下次见到你的小伙伴还在手写集合运算,直接把这篇丢给他。
完。

