likes
comments
collection
share

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

作者站长头像
站长
· 阅读数 15

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

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

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

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

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

免责声明

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

ES2015 的 JS Set

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

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

const languages = new Set(['JS', 'TS', 'HTML', 'JS'])

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

languages.size // 3

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

languages.add('JS')
languages.add('CSS')

languages.size // 4

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

languages.delete('TS')

languages.size // 3

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

languages.has('JS') // true
languages.has('TypeScript') // false

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

languages.forEach(element => console.log(element))

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

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

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

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 的并集是包含任一集合中存在的所有元素的集合。

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

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

Set.prototype.intersection(other)

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

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

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

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

交集就像 INNER JOIN

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

Set.prototype.difference(other)

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

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

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

Set.prototype.symmetricDifference(other)

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

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,排除两个表中的任何元素。

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

Set.prototype.isSubsetOf(other)

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

const frontEndLanguages = new Set(['JS', 'HTML', 'CSS'])
const declarativeLanguages = new Set(['HTML', 'CSS'])

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

集合也是其自身的子集。

frontEndLanguages.isSubsetOf(frontEndLanguages) // true

Set.prototype.isSupersetOf(other)

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

const frontEndLanguages = new Set(['JS', 'HTML', 'CSS'])
const declarativeLanguages = new Set(['HTML', 'CSS'])

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

集合也是其自身的超集。

frontEndLanguages.isSupersetOf(frontEndLanguages) // true

Set.prototype.isDisjointFrom(other)

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

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 点半更新,坚持阅读,自律打卡,每天一次,进步一点

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