ECMAScript 悄悄更新了两个对象分组 API,你注意到了么?

原创
01/26 15:30
阅读数 18

本文由体验技术团队小汪同学分享~


摘要:“ Map.groupBy() 静态方法使用提供的回调函数返回的值对给定可迭代对象中的元素进行分组。最终返回的 Map 使用测试函数返回的唯一值作为键,可用于获取每个组中的元素组成的数组。 详见: https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Map/groupBy

哈喽,各位小伙伴们大家好呀~

今天和大家聊一下谷歌在117版本更新的两个 API。

Object.groupBy 和 Map.groupBy

在日常的开发实践中,数据分组是一个很常用的操作。一般情况下,我们需要编写自定义的分组函数或者借助于三方库中的 groupBy 函数来实现。不过,有个激动人心的好消息:ECMAScript 更新了两个原生的分组方法——Object.groupBy 和 Map.groupBy。

让我们来回忆一下,在以往的开发过程中,我们是怎么自定义数组分组方法的。假设我们有一个由员工对象组成的数组,并且我们希望按照年龄来对这些员工进行分组。以往,我们可能会使用 forEach 循环来达到目的,相关代码如下所示:

const members = [
    { name: "HuaXiaoWen", age: 23 },
    { name: "HuaXiaoMing", age: 25 },
    { name: "HuaXiaoGang", age: 23 },
];

const membersGroupByAge = {};

members.forEach((member) => {
    const age = member.age;
    if (!membersGroupByAge[age]) {
        membersGroupByAge[age] = [];
    }
    membersGroupByAge[age].push(member);
});

console.log(membersGroupByAge);

执行上述代码,我们会得到以下的输出结果:

{  
    "23": [  
        {  
            "name": "HuaXiaoWen",  
            "age": 23  
        },  
        {  
            "name": "HuaXiaoGang",  
            "age": 23  
        }  
    ],  
    "25": [  
        {  
            "name": "HuaXiaoMing",  
            "age": 25  
        }  
    ]  
}

此外,我们也可以选择使用 reduce 方法来完成同样的任务:

const membersGroupByAge = members.reduce((accumulator, member) => {
    const age = member.age;
    if (!accumulator[age]) {
        accumulator[age] = [];
    }

    accumulator[age].push(member);
    return accumulator;
}, {});

显而易见的,无论是哪一种方法,代码都显得有些冗余且不够简洁。每当分组时,我们都需要检查分组键的值是否出现过,如果没出现过,则需要初始化一个空数组,并将符合条件的元素逐一添加进去。在这种实现下,代码的语义就会略显冗余。

使用 Object.groupBy 分组

Object.groupBy 方法,提供了一个直观的数组分组手段,不仅简化了开发者编写代码的过程,而且也使代码具有更好的可读性。利用这一新方法,我们可以非常方便地按照指定的属性对数组中的对象进行分组。

const membersGroupByAge = Object.groupBy(members, (member) => member.age);

通过上示代码,我们就可以轻松实现数据分组操作。

然而,值得我们注意的是,使用 Object.groupBy 方法返回的对象是一个“纯净”的对象——即没有继承任何原型链上的属性或方法。这一点非常关键,因为它意味着常见的 Object.prototype 方法,如 hasOwnProperty 或 toString,不会在这个对象上直接可用。虽然这样做有其好处,比如防止了属性名冲突的可能性,但同时也意味着我们在处理返回的分组对象时不能直接使用这些内置方法。例如:

const membersGroupByAge = Object.groupBy(members, (member) => member.age);

// 下面的代码会抛出错误,因为 `membersGroupByAge` 没有 `hasOwnProperty` 方法
console.log(membersGroupByAge.hasOwnProperty("28"));
// TypeError: membersGroupByAge.hasOwnProperty is not a function

// 即使用扩展运算符或者Object.assign来拷贝对象,新的对象仍然不会带有Object的方法
console.log({ ...membersGroupByAge }.hasOwnProperty("28"));
// TypeError: membersGroupByAge.hasOwnProperty is not a function

使用 Object.groupBy 时,需要注意回调函数应该返回 string 或 Symbol 类型的值,因为对象的 key 始终是 string 或 Symbol。如果回调函数返回其他类型的值(例如数字),key 值也会自动转换为字符串。在我们的例子中,即使 age 属性的值为数字,也会被转换为字符串类型,所以在访问该属性时需要注意这一点。

console.log(membersGroupByAge[23]); // 这是错误的访问方式,因为数字类型的键会被转换为字符串
// => undefined

console.log(membersGroupByAge["23"]); // 这才是正确的访问方式
// => [{"name":"HuaXiaoWen","age":23}, {"name":"HuaXiaoGang","age":23}]

使用 Map.groupBy 分组

Map.groupBy 方法,作为 ECMAScript 新特性的一员,与 Object.groupBy 承担着相似的职责,但它们返回的结果类型有所不同。Map.groupBy 方法的引入为开发者提供了更为灵活的数据结构——Map 对象。与返回普通对象的 Object.groupBy 相比,Map 对象具有一些独特的优势,特别是在进行键的比较时。

以如下代码为例,我们可以看到 Map.groupBy 方法如何根据个体的直接上级(汇报对象)来对人员进行分组:

const manager = { name: "HuaXiaoWen", age: 23, leader: null };
const develop = { name: "HuaXiaoMin", age: 25, leader: "manager" };
const member = [
    manager,
    develop,
    { name: "HuaXiaoGang", age: 25, leader: "manager" },
    { name: "HuaXiaoLiang", age: 23, leader: null }
];

const memberByLeader = Map.groupBy(members, (member) => member.leader);

在此示例中,我们依据每个人的领队来进行分组,得到的是一个 Map 对象,它以领队作为键,对应的直接下属列表作为值。但要注意,由于 Map 在进行键的比较时使用的是严格等价性检查(===),因此只有当两个对象的引用完全相同时,它们才被认为是等同的键。

memberByLeader.get(manager);
// => [{ name: "HuaXiaoWen", age: 23, leader: "manager" }, { name: "HuaXiaoGang", age: 23, leader: "manager" }]

memberByLeader.get({ name: "HuaXiaoWen", age: 23, leader: null });
// => undefined

浏览器兼容性

在近期的 JavaScript 发展趋势中,groupBy 方法作为 proposal-array-grouping 提案的一部分,已经引起了广泛关注。该提案目前正处在 ECMAScript 标准化进程的第三阶段,按照目前的进展,我们可以期待在2024年它能够正式纳入 ECMAScript 标准。

截至2023年12月4日,Google Chrome 浏览器的117版本已经率先支持了这一新特性,开发者可以在最新版的 Chrome 中体验到 Object.groupBy 和 Map.groupBy 方法。同时,Mozilla Firefox 的 Nightly 版本已在实验性质的标志后提供了这两个方法的实现。此外, Safari 浏览器也在其平台上以不同的命名实现了相似的功能。

这些方法在 Chrome 中的支持,意味着它们已经在 Google 的 V8引擎中得到了实现。V8引擎不仅是 Chrome 和 Chromium 浏览器的驱动核心,也是 Node.js 的基石。因此,随着 V8引擎的下一次更新,预计这些实用的 groupBy 方法也将很快在 Node.js 环境中可用。

为什么是 Object 的静态方法而不是 Array?

你可能会好奇为什么选择用 Object.groupBy 而不是像 Array.prototype.groupBy 这样的数组原型方法。

这是因为根据这个提案的说明,曾经有一个库尝试在 Array.prototype 上添加了一个不兼容的 groupBy 方法的补丁。出于向后兼容性的考虑,才将方法放在 Object.prototype 上。

JavaScript 向后兼容性是指在新版本的 JavaScript 中,旧版本的 JavaScript 代码仍然能够正常运行。这是非常重要的,因为很多网站和应用程序使用旧版本的 JavaScript 代码,在更新浏览器或者 JavaScript 引擎之后,这些代码可能会出现错误或者不兼容的情况。

1. 语言规范的兼容性: 新版本的 JavaScript 应该能够解析和执行旧版本的 JavaScript 代码,同时也应该保留旧版本的语言特性和语法规则。

2. API 的兼容性: 新版本的 JavaScript 应该保留旧版本的 API,使得旧版本的 JavaScript 应用程序能够正常运行,并且能够使用新版本的 API。

3. 浏览器兼容性: 新版本的 JavaScript 应该能够在各种浏览器中正常运行,同时也应该保留旧版本的浏览器兼容性,使得旧版本的 JavaScript 应用程序能够在不同的浏览器中正常运行。

类似的问题在之前尝试为 Array.prototype 添加 flatten 方法时也发生过,这导致了著名的“SmooshGate”争议。

简言之,通过采用静态方法,JavaScript 能够更加稳健地保持其向后兼容性,并且为将来可能引入的新功能和数据结构提供了更为灵活的扩展可能性。

关于 OpenTiny

OpenTiny 是一套企业级 Web 前端开发解决方案,提供跨端、跨框架、跨版本的 TinyVue 组件库,包含基于 Angular+TypeScript 的 TinyNG 组件库,拥有灵活扩展的低代码引擎 TinyEngine,具备主题配置系统TinyTheme / 中后台模板 TinyPro/ TinyCLI 命令行等丰富的效率提升工具,可帮助开发者高效开发 Web 应用。


欢迎加入 OpenTiny 开源社区。添加微信小助手:opentiny-official 一起参与交流前端技术~更多视频内容也可关注B站、抖音、小红书、视频号

OpenTiny 也在持续招募贡献者,欢迎一起共建

OpenTiny 官网https://opentiny.design/

OpenTiny 代码仓库https://github.com/opentiny/

TinyVue 源码https://github.com/opentiny/tiny-vue

TinyEngine 源码https://github.com/opentiny/tiny-engine

欢迎进入代码仓库 Star🌟TinyEngineTinyVueTinyNGTinyCLI~

如果你也想要共建,可以进入代码仓库,找到 good first issue标签,一起参与开源贡献~

展开阅读全文
加载中
点击引领话题📣 发布并加入讨论🔥
打赏
0 评论
0 收藏
0
分享
返回顶部
顶部