JavaScript 的新数组分组方法

原文链接:philna.sh/blog/2023/0...

对数组中的项目进行分组,你可能已经做过很多次了。每次都会手动编写一个分组函数,或者使用 lodashgroupBy 函数。

好消息是,JavaScript 现在有了分组方法,所以你再也不必这样做了。Object.groupByMap.groupBy 这两个新方法将使分组变得更简单,并节省我们的时间或依赖性。

以前的做法

假设你有一个代表人的对象数组,你想按年龄对它们进行分组。你可以这样使用 forEach 循环:

jsx 复制代码
const people = [
  { name: "Alice", age: 28 },
  { name: "Bob", age: 30 },
  { name: "Eve", age: 28 },
];

const peopleByAge = {};

people.forEach((person) => {
  const age = person.age;
  if (!peopleByAge[age]) {
    peopleByAge[age] = [];
  }
  peopleByAge[age].push(person);
});
console.log(peopleByAge);
/*
{
  "28": [{"name":"Alice","age":28}, {"name":"Eve","age":28}],
  "30": [{"name":"Bob","age":30}]
}
*/

或者可以像这样来使用reduce

jsx 复制代码
const peopleByAge = people.reduce((acc, person) => {
  const age = person.age;
  if (!acc[age]) {
    acc[age] = [];
  }
  acc[age].push(person);
  return acc;
}, {});

无论哪种方法,代码都略显笨拙。你总是要检查对象是否存在分组键,如果不存在,就用一个空数组来创建它。然后再将项目推入数组。

使用Object.groupBy

有了新的 Object.groupBy 方法,你就可以像这样得出结果:

jsx 复制代码
const peopleByAge = Object.groupBy(people, (person) => person.age);

简单多了!不过也有一些需要注意的地方。

Object.groupBy 返回一个空原型对象。这意味着该对象不继承 Object.prototype 的任何属性。这很好,因为这意味着你不会意外覆盖 Object.prototype 上的任何属性,但这也意味着该对象没有你可能期望的任何方法,如 hasOwnPropertytoString

javascript 复制代码
const peopleByAge = Object.groupBy(people, (person) => person.age);
console.log(peopleByAge.hasOwnProperty("28"));
// TypeError: peopleByAge.hasOwnProperty is not a function

传递给 Object.groupBy 的回调函数应返回字符串或Symbol。如果返回其他内容,则将强制转为字符串。

在我们的示例中,我们一直以数字形式返回age,但在结果中却被强制转为字符串。尽管如此,你仍然可以使用数字访问属性,因为使用方括号符号也会将参数强制为字符串。

jsx 复制代码
console.log(peopleByAge[28]);
// => [{"name":"Alice","age":28}, {"name":"Eve","age":28}]
console.log(peopleByAge["28"]);
// => [{"name":"Alice","age":28}, {"name":"Eve","age":28}]

使用Map.groupBy

除了返回 Map 之外,Map.groupBy 的功能与 Object.groupBy 几乎相同。这意味着你可以使用所有常用的 Map 函数。这也意味着你可以从回调函数返回任何类型的值。

jsx 复制代码
const ceo = { name: "Jamie", age: 40, reportsTo: null };
const manager = { name: "Alice", age: 28, reportsTo: ceo };

const people = [
  ceo,
  manager,
  { name: "Bob", age: 30, reportsTo: manager },
  { name: "Eve", age: 28, reportsTo: ceo },
];

const peopleByManager = Map.groupBy(people, (person) => person.reportsTo);

在本例中,我们是按照向谁汇报工作来对人员进行分组的。请注意,要从该 Map 中按对象检索项目,对象必须具有相同的引用。

jsx 复制代码
peopleByManager.get(ceo);
// => [{ name: "Alice", age: 28, reportsTo: ceo }, { name: "Eve", age: 28, reportsTo: ceo }]
peopleByManager.get({ name: "Jamie", age: 40, reportsTo: null });
// => undefined

在上面的示例中,第二行使用了一个看起来像 ceo 对象的对象,但它并不是同一个对象,因此它不会从 Map 中返回任何内容。要想成功地从 Map 中获取项目,请确保你保留了要用作键的对象的引用。

何时可用

这两个 groupBy 方法是 TC39 提议的一部分,目前处于第三阶段。这意味着它很有可能成为一项标准,因此也出现了一些实施方案。

Chrome 浏览器 117 版本刚刚推出了对这两种方法的支持,而 Firefox 浏览器 119 版本也发布了对这两种方法的支持。Safari 以不同的名称实现了这些方法,我相信他们很快就会更新。既然 Chrome 浏览器中出现了这些方法,就意味着它们已在 V8 中实现,因此下次 V8 更新时,Node 中也会出现这些方法。

为什么使用静态方法

你可能会问,为什么要以 Object.groupBy 而不是 Array.prototype.groupBy 的形式来实现呢?根据该提案,有一个库曾经用一个不兼容的 groupBy 方法对 Array.prototype 进行了猴子补丁。在考虑新的应用程序接口时,向后兼容性非常重要。几年前,在尝试实现 Array.prototype.flatten 时,这一点在一次被称为 SmooshGate 的事件中得到了强调。

幸运的是,使用静态方法似乎更有利于未来的可扩展性。当 Record 和 Tuples 提议实现时,我们可以添加一个 Record.groupBy 方法,用于将数组分组为不可变的记录。

总结

将项目分组显然是我们开发人员的一项重要工作。目前,每周从 npm 下载 lodash.groupBy 的次数在 150 万到 200 万之间。很高兴看到 JavaScript 填补了这些空白,让我们的工作变得更加轻松。

现在,下载 Chrome 117 并亲自尝试这些新方法吧。

相关推荐
张元清5 小时前
React useResizeObserver Hook:追踪元素尺寸变化(2026)
javascript·react.js
瑞瑞小同学7 小时前
处理echarts x轴内容过多,重叠问题
前端·javascript·echarts
像我这样帅的人丶你还8 小时前
MCP + npm:给五年前的老系统接上AI
前端·javascript·agent
一个写前端的9 小时前
基于 Vue 3 的大文件分片上传组件,支持断点续传、并发上传、失败重试等功能
前端·javascript·vue.js
猫头_10 小时前
AI 流式传输工程指南:有了 EventSource 为何还要 Fetch?
javascript·http·llm
良木林11 小时前
滑动窗口 - LeetCode hot 100
javascript·算法·leetcode·双指针·滑动窗口
不简说12 小时前
# JS 代码技巧 vol.7 — 20 个浏览器 API 实战,自带 API 能干的事别自己封装
前端·javascript·面试
Hilaku13 小时前
为什么 Cloudflare 能做到毫秒级冷启动,而其它云厂商却不行?
前端·javascript·程序员
名字还没想好☜13 小时前
React useLayoutEffect vs useEffect:页面闪一下的元凶与正确修法
前端·javascript·react.js·react·hooks
2301_7944615713 小时前
JavaScript 基础知识点详解
前端·javascript·html