likes
comments
collection
share

JS 数组新方法:分组提案幕后的技术细节

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

给前端以福利,给编程以复利。大家好,我是大家的林语冰。

00. 技术背景

ES2024 最新推出的 JS 新功能里,原生支持了一个类似 lodash _.groupBy() 方法的数组分组提案。

有趣的是,groupBy() API 虽然名为“数组分组”提案,但最终却和数组毫无关系,反而作为 ObjectMap 的静态方法来实现。因此,本期我们就一起来深度学习一下,数组分组提案中奇奇怪怪的技术细节。

01. 使用指南

数组分组方法源自 proposal-array-grouping 提案,目前已经到达 stage 4,正式成为 ES2024 的标准内置功能。

数据分组是一个常见的业务场景,从简单的奇偶分组,或者统计某个字符的频率,再到数据库查询专属的 GROUP BY SQL 等都有所涉及。在 JS 中,某些 API 天然就返回可能需要分组的数据。

举个栗子,Promise.allSettled() 方法会返回包含所有状态的实例结果,不管对应实例是成功或失败都一视同仁,这种场景可能需要继续分组处理:

JS 数组新方法:分组提案幕后的技术细节

上述代码中,data 是包含所有状态的实例结果,返回值的固定格式如下所示:

JS 数组新方法:分组提案幕后的技术细节

ES2024 之前,如果我们想要根据状态对结果继续分组,相关方案包括但不限于:

  • 手动硬编码分组
  • 循环迭代分组
  • 借助第三方库

举个栗子,我们可以试试手动硬编码:

JS 数组新方法:分组提案幕后的技术细节

可以看到,通过对每种状态手动过滤,就可以按需分组。但是,这种方案并不灵活,首先,数据量越大,重复代码越多,不好维护;其次,并非所有数据结构都有类似数组的过滤方法,那就必须先通过转换为数组来“曲线救国”。

ES2024 之后,针对上述使用场景,我们可以直接使用 Object.groupBy() 静态分组方法,一行代码即可搞定。

JS 数组新方法:分组提案幕后的技术细节

可以看到,Object.groupBy() 静态方法的第一个参数是需要分组的数据,第二个参数是回调函数,允许自定义分组的逻辑。

这里我们根据状态分组,分为 'fulfilled''rejected' 两组,结果如下所示:

JS 数组新方法:分组提案幕后的技术细节

02. 技术细节

ES2024 目前的分组方法同时支持 ObjectMap,两者功能基本相同,只是针对不同的数据结构实现:

  • Object.groupBy()
  • Map.groupBy()

粉丝请注意,调用 Object.groupBy() 会返回一个 空原型对象(null-prototype object),而调用 Map.groupBy() 则会返回一个 Map 对象。

空原型对象也被称为“裸对象”,指的是没有从 Object 继承任何属性的对象,可以通过 Object.create() 等方法创建出来。

JS 数组新方法:分组提案幕后的技术细节

裸对象的优势在于解构时符合人体工程学,可以防止与继承属性产生冲突,ES6 之前有时也会作为 Map 的低配替代品。

groupBy() 的 API 设计来看,调用不同数据结构的静态方法,返回对应数据结构的结果,这样的返回值还是比较符合直觉的。但是参数方面就比较反直觉了,第一个参数除了常用的数组之外,其实所有可迭代对象都能支持。

举个栗子,我们对一个数字字符串进行奇偶分组:

JS 数组新方法:分组提案幕后的技术细节

可以看到,第一个参数使用的是字符串,而不是数组,因为字符串也是可迭代的。

另外一个细节在于,由于该方法的返回值是对象,而对象的键能且仅能支持 StringSymbol 类型,所以分组后的结果对象会根据回调函数的返回值,按需隐式类型转换。

上述代码中,由于是奇偶分组,所以返回值表示余数:要么为 0(说明是偶数),要么为 1(说明是奇数)。但是 Number 不是对象键的合法类型,所以余数会被转换为字符串类型的 '0''1',如下图所示:

这也是 Map.groupBy() 的设计动机之一,因为不同于普通对象,Map 支持任意类型的键,所以结果对象的键会根据回调函数的返回值原封不动地保留,且不会触发隐式类型转换。

03. 提案风波

ES6 之后,每个 JS 提案的诞生,从来都不是一帆风顺的,数组分组提案也不例外。

数组分组提案的两个 API 和数组毫无关系,且第一个参数其实可以接受包括数组在内的可迭代对象,某种意义上,命名为“可迭代对象分组”,说不定更名副其实。

另一个设计细节在于,groupBy() API 被实现为 Object.groupBy() 等静态方法,而不是 Object.prototype.groupBy() 等实例方法。

其实,早期的数组分组提案直接基于数组的实例方法实现,其函数签名大概长这样:

JS 数组新方法:分组提案幕后的技术细节

可以看到,这确实是名副其实的数组分组提案!那为何最终提案变成今天这个设计呢?

主要有两个原因,一个是面向过去的“历史包袱”,一个是面向未来的扩展设计。“历史包袱”在于,Array.prototype.groupBy() 这个名字,过去已经被某些第三方库的“代码屎山”无情占用了,所以命名冲突会直接导致兼容性问题。同时,现在这种设计提供了一种优秀的方法,未来可以灵活支持 Tuples(元组)等提案。

另外,对于 lodash 库的用户而言,从外部的 _.groupBy() 迁移到内置支持的 Object.groupBy() 也更加丝滑,毕竟都是“静态方法”。

04. 高潮总结

总体而言,使用 lodash 的 _.groupBy() 方法也问题不大,事实上,大数据告诉我,这个方法人气爆棚,npm 每周下载量高达 200 万次。

JS 数组新方法:分组提案幕后的技术细节

但是,依赖第三方库会增加打包体积,且性能也不如原生实现,所以最终催生了数组分组提案,并且成功标准化。

兼容性方面,我也做了临床测试,除了“万恶之源”IE 浏览器(已退役),主流浏览器全都广泛支持,大家可以体验一下。

JS 数组新方法:分组提案幕后的技术细节

参考文献

  1. GitHubgithub.com/tc39/propos…
  2. MDNdeveloper.mozilla.org/en-US/docs/…
  3. Lodashlodash.com

粉丝互动

本期话题是:如何评价 ES2024 最新功能:数组分组方法?你可以在本文下方自由言论,或者转发分享。

欢迎持续关注“前端俱乐部”,给前端以福利,给编程以复利。

坚持阅读的小伙伴可以给自己点赞!谢谢大家的点赞和分享,掰掰~

JS 数组新方法:分组提案幕后的技术细节

转载自:https://juejin.cn/post/7392501443322363938
评论
请登录