JS 即将推出的 Set 新特性:交并差集

大家好,这里是大家的林语冰。

JS 中的 Set(集合)在 ES2015 规范中首次引入,但它一直美中不足。这种不上不下的现状即将改变。

Set 是值的集合,其中每个值能且仅能出现一次。在 Set 的 ES2015 版本中,实用功能主要围绕创建、添加、删除以及检查 Set 的成员。如果我们想对多个集合进行操作或比较,那就必须自己编写工具函数。幸运的是,TC39 委员会(为制定 ECMAScript 规范而成立的委员会)和浏览器一直在致力于此。我们现在可以在 JS 提案中看到诸如 unionintersectiondifference 之类的全新函数。

在了解新型功能之前,我们先来温习一下 JS 中 Set 目前的功能,然后我们会在下文介绍全新的 Set 函数,以及已经支持的 JS 引擎。

免责声明

本文属于是语冰的直男翻译了属于是,略有删改,仅供粉丝参考。英文原味版请传送 Union, intersection, difference, and more are coming to JavaScript Sets

ES2015 的 JS Set

我们来瞄一下 ES2015 版本的 JS Set 能做什么。

我们可以构造一个没有任何参数的 Set,这会返回一个空集。或者,我们可以提供一个数组之类的可迭代对象来初始化 Set

js 复制代码
const languages = new Set(['JS', 'TS', 'HTML', 'JS'])

Set 能且仅能包含唯一值,因此虽然我们在上面传递了 4 个元素,但 Set 有且仅有 3 个不会重复的唯一成员。我们可以使用 size 属性来证明。

js 复制代码
languages.size // 3

我们可以使用 add 函数向 Set 添加更多元素。添加 Set 中已存在的元素则不会执行任何操作。

js 复制代码
languages.add('JS')
languages.add('CSS')

languages.size // 4

我们可以使用 deleteSet 中删除元素。

js 复制代码
languages.delete('TS')

languages.size // 3

我们还可以使用 has 函数检查某个元素是否是 Set 的成员。Set 的好处之一在于,这种检查可以在常数时间复杂度 O(1) 内完成,而检查元素是否在 Array 中需要根据 Array 的长度在线性时间复杂度 O(n) 内完成。使用 Set 来完成这样的任务是一种有意编写高效代码的精简方法。

js 复制代码
languages.has('JS') // true
languages.has('TypeScript') // false

我们可以使用 forEachfor...of 循环遍历 Set 的元素。元素按照添加到 Set 的顺序排序。

js 复制代码
languages.forEach(element => console.log(element))

// "JS"
// "HTML"
// "CSS"

我们还可以使用等价的 keysvalues 函数,以及 entriesSet 获取迭代器功能。

最后,我们可以使用 clear 函数重置清空 Set

js 复制代码
languages.clear()

languages.size // 0

综上所述,我们看到了可以使用 ES2015 规范版本的 Set 做什么:

  • Set 提供了处理唯一值集合的方法
  • 将元素添加到 Set,并测试它们在 Set 中的存在是有效的
  • Array 或其他可迭代对象转换为 Set 是过滤重复元素的简单方法

不过,ES2015 的实现错过了 Set 集合之间的操作。我们可能想要创建一个 Set,其中包含来自其他两个 Set 的所有元素,即两个 Set 的并集;或者找出两个 Set 具有的共同点元素,即交集;抑或是找出一个集合中不存在、但在另一个集合中存在的元素,即差集。直到最近,我们还必须自己实现相应的工具函数。

全新的 Set 功能有哪些?

Set 方法提案将以下方法添加到 Set 实例中:

  • union
  • intersection
  • difference
  • symmetricDifference
  • isSubsetOf
  • isSupersetOf
  • isDisjointFrom

其中某些方法类似于某些数据库操作,我们将用它们来说明对应代码的结果。让我们瞄一下每个函数对应功能的若干示例。

您可以在 Chrome 122+ 或 Safari 17+ 中体验下文的任何代码示例。

Set.prototype.union(other)

Set 的并集是包含任一集合中存在的所有元素的集合。

js 复制代码
const frontEndLanguages = new Set(['JS', 'HTML', 'CSS'])
const backEndLanguages = new Set(['Go', 'JS'])

const allLanguages = frontEndLanguages.union(backEndLanguages)
// => Set {"JS", "HTML", "CSS", "Go"}

在此示例中,前两个集合中的所有语言都在第三个并集中。与向 Set 添加元素的其他方法一样,重复元素会被删除。

这相当于两个表之间的 SQL FULL OUTER JOIN

Set.prototype.intersection(other)

交集是包含两个集合中同时存在的所有元素的集合。

js 复制代码
const frontEndLanguages = new Set(['JS', 'HTML', 'CSS'])
const backEndLanguages = new Set(['Go', 'JS'])

const frontAndBackEnd = frontEndLanguages.intersection(backEndLanguages)
// => Set {"JS"}

元素 'JS' 是这两个集合中同时存在的唯一元素。

交集就像 INNER JOIN

Set.prototype.difference(other)

一个集合与另一个集合之间的差集是,第一个集合中存在、但另一个集合中不存在的所有元素。

js 复制代码
const frontEndLanguages = new Set(['JS', 'HTML', 'CSS'])
const backEndLanguages = new Set(['Go', 'JS'])

const onlyFrontEnd = frontEndLanguages.difference(backEndLanguages)
// => Set {"HTML", "CSS"}

const onlyBackEnd = backEndLanguages.difference(frontEndLanguages)
// => Set {"Go"}

在查找集合之间的差集时,重要的是我们在哪个集合上调用函数,以及哪个集合是参数。在上述示例中,从前端语言中删除后端语言会导致 'JS' 被删除,并在结果集中返回 'HTML''CSS'。然而,从后端语言中删除前端语言仍然会导致 'JS' 被删除,并返回 'Go'

差集就像执行 LEFT JOIN

Set.prototype.symmetricDifference(other)

两个集合之间的对称差集是一个集合,该集合包含存在于两个集合之一中的元素,但不包含两个集合中同时存在的元素。

js 复制代码
const frontEndLanguages = new Set(['JS', 'HTML', 'CSS'])

const backEndLanguages = new Set(['Go', 'JS'])

const onlyFrontEnd = frontEndLanguages.symmetricDifference(backEndLanguages)
// => Set {"HTML", "CSS", "Go"}

const onlyBackEnd = backEndLanguages.symmetricDifference(frontEndLanguages)
// => Set {"Go", "HTML", "CSS"}

在这种场景下,结果集中的元素是相同的,但粉丝请注意,集合元素的顺序是不同的。集合顺序由元素添加到集合的顺序决定,并且执行该函数的集合会优先添加其元素。

对称差异类似于 FULL OUTER JOIN,排除两个表中的任何元素。

Set.prototype.isSubsetOf(other)

如果第一个集合中的所有元素都出现在第二个集合中,那么第一个集合就是第二个集合的子集。

js 复制代码
const frontEndLanguages = new Set(['JS', 'HTML', 'CSS'])
const declarativeLanguages = new Set(['HTML', 'CSS'])

declarativeLanguages.isSubsetOf(frontEndLanguages) // true
frontEndLanguages.isSubsetOf(declarativeLanguages) // false

集合也是其自身的子集。

js 复制代码
frontEndLanguages.isSubsetOf(frontEndLanguages) // true

Set.prototype.isSupersetOf(other)

如果第二个集合中的所有元素都出现在第一个集合中,那么第二个集合是第一个集合的超集。这是子集的相反关系。

js 复制代码
const frontEndLanguages = new Set(['JS', 'HTML', 'CSS'])
const declarativeLanguages = new Set(['HTML', 'CSS'])

declarativeLanguages.isSupersetOf(frontEndLanguages) // false
frontEndLanguages.isSupersetOf(declarativeLanguages) // true

集合也是其自身的超集。

js 复制代码
frontEndLanguages.isSupersetOf(frontEndLanguages) // true

Set.prototype.isDisjointFrom(other)

最后,如果一个集合与另一个集合没有共同元素,那么它们是不相交的。

js 复制代码
const frontEndLanguages = new Set(['JS', 'HTML', 'CSS'])
const interpretedLanguages = new Set(['JS', 'Ruby', 'Python'])
const compiledLanguages = new Set(['Java', 'C++', 'TS'])

interpretedLanguages.isDisjointFrom(compiledLanguages) // true
frontEndLanguages.isDisjointFrom(interpretedLanguages) // false

这些集合中的解释型语言和编译型语言不重叠,因此这些集合是不相交的。前端语言和解释语言确实与 'JS' 元素重叠,因此它们并不是不相交的。

兼容性支持

目前,Set 新方法的相关提案处于 TC39 流程的第 3 阶段,苹果 Safari 17 去年 9 月已经发布,谷歌 Chrome 122 今年 2 月也已经实现了这些方法。微软 Edge 紧随 Chrome,Firefox Nightly 也有支持,估计这两种浏览器很快也会提供支持。

Bun 使用的是 Safari 的 JavaScriptCore 引擎,因此 Bun 已经支持这些新功能。Chrome 中的支持意味着它已被添加到 V8 JS 引擎中,且不久后会被 Node 采用。

这意味着,该提案大概率会进入流程的第 4 阶段,甚至可能在 ES2024 规范最终定稿之前及时加入。

Polyfills(功能补丁)

当我们需要较旧的 JS 引擎支持,我们可以使用 Polyfill 来升级到这些函数的规范实现。它们可以在 core-js 中使用,也可以作为 es-shims 项目中每个功能的单独包使用。举个栗子,Set.prototype.union 包可用于交集功能。

如果您已经编写了任何这些函数的自定义实现,我建议您首先升级到 polyfill,然后随着运行时支持变得更加广泛,再逐步淘汰它们。

Set 更加完备

JS 中的 Set 长期以来一直美中不足,但这 7 个全新函数很好地完善了 Set 的实现。将这样的功能构建到语言中意味着,我们可以减少对第三方模块或我们自己的实现的依赖,并且可以关注我们正在尝试解决的问题。

本期话题是 ------ 你最喜欢 Set 的哪个新方法?

欢迎在本文下方自由言论,文明共享。谢谢大家的点赞,掰掰~

《前端猫猫教》每日 9 点半更新,坚持阅读,自律打卡,每天一次,进步一点

相关推荐
ywf12151 小时前
前端的dist包放到后端springboot项目下一起打包
前端·spring boot·后端
恋猫de小郭1 小时前
2026,Android Compose 终于支持 Hot Reload 了,但是收费
android·前端·flutter
hpoenixf7 小时前
2026 年前端面试问什么
前端·面试
还是大剑师兰特7 小时前
Vue3 中的 defineExpose 完全指南
前端·javascript·vue.js
泯泷7 小时前
阶段一:从 0 看懂 JSVMP 架构,先在脑子里搭出一台最小 JSVM
前端·javascript·架构
mengchanmian8 小时前
前端node常用配置
前端
华洛8 小时前
利好打工人,openclaw不是企业提效工具,而是个人助理
前端·javascript·产品经理
xkxnq9 小时前
第六阶段:Vue生态高级整合与优化(第93天)Element Plus进阶:自定义主题(变量覆盖)+ 全局配置与组件按需加载优化
前端·javascript·vue.js
A黄俊辉A9 小时前
vue css中 :global的使用
前端·javascript·vue.js
小码哥_常10 小时前
被EdgeToEdge适配折磨疯了,谁懂!
前端