likes
comments
collection
share

JavaScript正在拥有数组的分组方法

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

本文为翻译文章,原文为: JAVASCRIPT IS GETTING ARRAY GROUPING METHODS 是 Phil Nash发布于2023年9月14日的文章。

在数组中对元素进行分组可能是我们已经做过很多次的事情之一。每次都需要手工编写分组函数,或者使用 loash 的 groupBy 函数

一个好消息是: JavaScript 现在正在获得分组方法。所以您不再需要像之前那样做了。Object.GroupByMap.groupBy 是新的方法,它们可以使分组变得更容易,节省我们的时间或依赖项。

现有的分组方法

假设你有一个代表人的对象数组,你想根据他们的年龄对他们进行分组。您可以像下面这样使用 forEach 循环:

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,像这样:

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 方法,您可以这样做:

const peopleByAge = Object.groupBy(people, (person) => person.age);

简单多了! 虽然有些事情需要注意:

GroupBy 返回一个原型为空(null)的对象。这意味着该对象不继承 Object.prototype 的任何属性。这样设计的好处是您不会意外地覆盖 Object.prototype 上的任何属性。但是这也意味着对象没有您可能期望的任何方法,比如 hasOwnProperty 或 toString。例如:

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

传递给 Object.groupBy 的回调函数应该返回字符串或Symbol。如果它返回任何其他内容,它将被强制转换为一个字符串。

在示例中,将年龄作为一个数字返回,但是在结果中它被强制为字符串。尽管仍然可以使用数字访问这些属性,因为使用方括号表示法也会将参数强制转换为字符串:

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进行分组

GroupBy 所做的事情与 Object.groupBy 几乎相同,只是返回一个 Map。这意味着您可以使用所有常用的 Map 函数。它还意味着可以从回调函数返回任何类型的值:

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 检索项,对象必须具有相同的标识:

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方法

这两个 groupBy 方法目前处于TC39提案的第3阶段。这意味着它很有可能成为一个标准,因此会出现一些实现。

Chrome 117刚刚发布了对这两种方法的支持,Firefox Nightly在一个Flag后实现了它们。Safari 已经用不同的名称实现了这些方法,我相信它们很快就会更新。因为这些方法都在 Chrome 中,这意味着它们已经在 V8中实现了,所以下次 V8更新时 Node 中也会有这些方法。

为什么使用静态方法

您可能会思考这为什么作为 Object.groupBy 而不是 Array.Prototype.groupBy 实现的。根据提案,现在有一个库用一个不兼容的 groupBy 方法来给 Array.prototype打补丁。当考虑新的web API 时,向后兼容性非常重要的。几年前在一个名为 SmooshGate 的事件中,当试图实现 Array.Prototype.flatten 时,这一点就得到了强调。

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

JAVASCRIPT 正在填补空白

将元素分组到在一起显然是我们作为开发人员要做的一件重要的事情。lodash.groupBy目前在npm上的下载量高达150万到200万次。很高兴看到 JavaScript 正在填补这项空白,并使我们的工作更容易。

现在,去拿 Chrome117,自己试试这些新方法吧。