本文翻译自 Union, intersection, difference, and more are coming to JavaScript Sets,作者:Phil Nash, 略有删改。
JavaScript Set
在 ES2015 规范中引入,但一直感觉不完整。
Set
是一种值的集合,其中每个值只能出现一次。在 ES2015 版本的 Set
中,可用的功能围绕创建、添加、移除以及检查 Set
的成员资格。如果你想操作或比较多个集合,需要自己实现相关逻辑。幸运的是,负责处理 ECMAScript 规范的 TC39 委员会和浏览器一直在努力解决这个问题。我们现在在 JavaScript 实现中看到了 union
、intersection
和 difference
等函数。
在我们查看新功能之前,先来回顾一下现在的 Set
现在能做什么,然后我们将深入了解新的 Set
函数以及支持它们的 JavaScript 引擎。
ES2015 Set 能做什么?
让我们看看 ES2015 版本的 Set
能做什么。
你可以不带任何参数构造一个 Set
,这会给你一个空的 Set
。或者你可以提供一个可迭代对象,如数组,来初始化 Set
。
const languages = new Set(["JavaScript", "TypeScript", "HTML", "JavaScript"]);
Set
只能包含唯一值,所以上面的 Set
最终只有三个值。你可以用 size
属性检查这一点。
languages.size;
// => 3
你可以用 add
函数向 Set
添加更多元素。如果 Set
中已存在要添加的元素,则不执行任何操作。
languages.add("JavaScript");
languages.add("CSS");
languages.size;
// => 4
你可以使用 delete
从 Set
中移除元素。
languages.delete("TypeScript");
languages.size;
// => 3
你可以使用 has
函数检查元素是否是 Set
的成员。Set
的一个好处是这个检查可以在常数时间内完成(O(1)),而检查元素是否在 Array
中的时间则取决于 Array
的长度(O(n))。对于这样的任务,使用 Set
集合是一种编写高效代码的好方法。
languages.has("JavaScript");
// => true
languages.has("TypeScript");
// => false
你可以使用 forEach
或 for...of
循环遍历 Set
的元素。元素按它们被添加到 Set
的顺序排序。
languages.forEach(element => console.log(element));
// "JavaScript"
// "HTML"
// "CSS"
你还可以使用 keys
和 values
函数(实际上是等效的)以及 entries
函数从 Set
获取迭代器。
最后你可以用 clear
函数清空一个 Set
。
languages.clear();
languages.size;
// => 0
这是对 ES2015 规范版本 Set
功能的一个回顾:
Set
提供了处理唯一值集合的方法- 向
Set
添加元素以及测试它们在Set
中的存在是高效的 - 将
Array
或其他可迭代对象转换为Set
是过滤重复项的简单方法
通过以上回顾可以发现缺少了 Set
之间的操作。你可能想要创建一个包含两个其他 Set
所有项的 Set
(两个 Set
的联合),找出两个 Set
共同拥有的内容(交集),或者找出在一个 Set
中但不在另一个 Set
中的内容(差集)。
新的Set功能有哪些?
Set
方法提案为 Set
实例添加了以下方法:union
(联合)、intersection
(交集)、difference
(差集)、symmetricDifference
(对称差集)、isSubsetOf
(是子集)、isSupersetOf
(是超集)和 isDisjointFrom
(不相交)。
这些方法中的一些类似于 SQL 连接,让我们看一些每个函数的例子。
你可以在 Chrome 122+
或 Safari 17+
中尝试下面的任何代码示例。
Set.prototype.union(other)
集合的联合是一个包含两个集合中所有元素的集合。
const frontEndLanguages = new Set(["JavaScript", "HTML", "CSS"]);
const backEndLanguages = new Set(["Python", "Java", "JavaScript"]);
const allLanguages = frontEndLanguages.union(backEndLanguages);
// => Set {"JavaScript", "HTML", "CSS", "Python", "Java"}
在这个例子中,前两个集合中的所有语言都在第三个集合中。与其他向 Set
添加元素的方法一样,重复项会被删除。
这相当于两个表之间的 SQL FULL OUTER JOIN
。
Set.prototype.intersection(other)
交集是包含两个集合中都存在的所有元素的集合。
const frontEndLanguages = new Set(["JavaScript", "HTML", "CSS"]);
const backEndLanguages = new Set(["Python", "Java", "JavaScript"]);
const frontAndBackEnd = frontEndLanguages.intersection(backEndLanguages);
// => Set {"JavaScript"}
"JavaScript" 是这里两个集合中都存在的唯一元素。
交集就像是一个 INNER JOIN
。
Set.prototype.difference(other)
你正在使用的集合与另一个集合之间的差集是第一个集合中存在而第二个集合中不存在的所有元素。
const frontEndLanguages = new Set(["JavaScript", "HTML", "CSS"]);
const backEndLanguages = new Set(["Python", "Java", "JavaScript"]);
const onlyFrontEnd = frontEndLanguages.difference(backEndLanguages);
// => Set {"HTML", "CSS"}
const onlyBackEnd = backEndLanguages.difference(frontEndLanguages);
// => Set {"Python", "Java"}
在寻找集合之间的差集时,调用函数的集合和作为参数的集合很重要。在上面的例子中,从前端语言中移除后端语言会移除 "JavaScript" 并返回结果集中的 "HTML" 和 "CSS"。而从后端语言中移除前端语言仍然会移除 "JavaScript",并返回 "Python" 和 "Java"。
差集就像执行一个 LEFT JOIN
。
Set.prototype.symmetricDifference(other)
两个集合之间的对称差集是一个包含两个集合之一但不包含两者的集合。
const frontEndLanguages = new Set(["JavaScript", "HTML", "CSS"]);
const backEndLanguages = new Set(["Python", "Java", "JavaScript"]);
const onlyFrontEnd = frontEndLanguages.symmetricDifference(backEndLanguages);
// => Set {"HTML", "CSS", "Python", "Java"}
const onlyBackEnd = backEndLanguages.symmetricDifference(frontEndLanguages);
// => Set {"Python", "Java", "HTML", "CSS"}
在这种情况下,结果集中的元素是相同的,但请注意顺序不同。集合的顺序由元素添加到集合的顺序决定,执行函数的集合的元素将首先被添加。
对称差集就像一个排除了两个表中都存在的元素的 FULL OUTER JOIN
。
Set.prototype.isSubsetOf(other)
如果一个集合中的所有元素都出现在另一个集合中,则该集合是另一个集合的子集。
const frontEndLanguages = new Set(["JavaScript", "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(["JavaScript", "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(["JavaScript", "HTML", "CSS"]);
const interpretedLanguages = new Set(["JavaScript", "Ruby", "Python"]);
const compiledLanguages = new Set(["Java", "C++", "TypeScript"]);
interpretedLanguages.isDisjointFrom(compiledLanguages);
// => true
frontEndLanguages.isDisjointFrom(interpretedLanguages);
// => false
这些集合中的解释型语言和编译型语言没有重叠,所以它们是不相交的。前端语言和解释型语言有 "JavaScript" 这个共同元素,所以它们不是不相交的。
支持情况
截至撰写本文时,该提案在 TC39 的过程中处于第 3 阶段,Safari 17(2023年9月发布)和 Chrome 122(2024年2月)已经发布了这些方法的实现。Edge 紧随 Chrome,Firefox Nightly 也有支持,我预计这两种浏览器很快也会支持。
希望这意味着该提案将进入该过程的第 4 阶段,甚至可能及时加入 ES2024 规范。
Polyfills
虽然你需要旧的 JavaScript 引擎支持,但可以使用polyfill来升级这些函数的规范兼容实现。它们在 core-js
中可用,或者作为每个函数的单独包在 es-shims 项目中(例如,set.prototype.union
包可用于联合功能)。
Set 不再感觉不完整
长期以来,JavaScript Set
一直感觉不完整,但这些 7 个新函数很好地完善了实现。将这样的功能使用到开发中意味着我们将减少很多相关依赖。
看完本文如果觉得有用,记得点个赞支持,收藏起来说不定哪天就用上啦~
专注前端开发,分享前端相关技术干货,公众号:南城大前端(ID: nanchengfe)
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。