在处理数组时,有时我们需要将其中的项目按照某个特定的属性或条件进行分类或分组。这个过程可能会多次重复,每次都需要编写分组函数或使用像 lodash 这样的库中的 groupBy 函数来完成。但是,现在 JavaScript 正在引入一种更方便的方法,让我们不再需要手动编写这些分组逻辑。
JavaScript 引入了新的 Object.groupBy 和 Map.groupBy 方法,它们使得对数组进行分组变得更加简单和高效。这意味着我们不再需要花费大量时间去编写繁琐的分组代码,也不需要依赖外部库。这些新方法将帮助我们更轻松地完成分组操作。
假设你有一个表示人员信息的对象数组,并且你想按照他们的年龄对他们进行分组。你可以像这样使用一个循环:
// 创建一个包含个人信息的数组
const people = [
{ name: "Alice", age: 28 },
{ name: "Bob", age: 30 },
{ name: "Eve", age: 28 },
];
// 创建一个空对象,用于存储按年龄分组后的结果
const peopleByAge = {};
// 使用forEach遍历people数组中的每个个人对象
people.forEach((person) => {
// 获取当前个人对象的年龄
const age = person.age;
// 检查peopleByAge对象中是否已经存在以当前年龄为键的数组
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 函数,就像这样:
// 使用reduce函数对people数组进行处理,初始累加器(acc)为空对象{}
const peopleByAge = people.reduce((acc, person) => {
// 获取当前个人对象的年龄
const age = person.age;
// 检查acc对象中是否已经存在以当前年龄为键的数组
if (!acc[age]) {
// 如果不存在,创建一个以当前年龄为键的空数组
acc[age] = [];
}
// 将当前个人对象添加到对应年龄的数组中
acc[age].push(person);
// 返回更新后的累加器对象
return acc;
}, {}); // 初始累加器为空对象
// 执行完reduce后,peopleByAge对象包含按年龄分组的结果
console.log(peopleByAge);
这段代码使用了reduce函数,它将people数组中的每个个人对象依次传递给回调函数,并在每次迭代中更新累加器对象acc。最终,peopleByAge对象包含了按年龄分组的结果,其结构与之前的示例相同。这种方法可以更紧凑和函数式地实现相同的逻辑。
无论使用哪种方式,这段代码确实存在一些重复的模式。具体来说,代码需要不断检查对象中是否已经存在与年龄对应的键,如果不存在则创建一个空数组,并将当前个人对象推入该数组。
通过这种新的方法,你可以得到这样的结果:
const peopleByAge = Object.groupBy(people,
(person) => person.age);
简单多了!虽然还有一些需要注意的事情。
Object.groupBy 返回的是一个空原型对象,这意味着这个对象不会继承任何来自 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 的回调函数应返回 string 或 Symbol 。如果返回其他任何值,将被强制转换为 string 。
在我们的例子中,我们一直将 age 返回为 number ,但在结果中它被强制转换为 string 。尽管您仍然可以使用 number 访问属性,因为使用方括号表示法也会强制转换参数为 string 。
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 几乎和 Object.groupBy 做的事情一样,只是它返回一个 Map 对象而不是普通对象。这就意味着你可以使用所有常规的 Map 方法来处理它,就像你处理其他 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 中检索项目,请确保您保留对您想要用作键的对象的引用。这意味着您需要确保键对象是同一个,而不是一个相似但不同的对象。这是因为在 JavaScript 中,对象的引用是唯一的,只有引用相同才能够准确地从 Map 中检索数据。
两种 groupBy 方法是 JavaScript 社区的一项新提案,目前处于标准化进程的第3阶段。这意味着它们有很大机会成为 JavaScript 标准的一部分,而且已经在一些主要的浏览器中开始实现了。例如,最新版本的 Chrome(Chrome 117)已经支持这两种方法,而 Firefox Nightly 版本也已经在试验中实现了它们。
另外,Safari 浏览器已经以不同的名称实现了这些方法,这意味着它们也将很快在 Safari 中更新。由于这些方法已经在 Chrome 中实现,这意味着它们已经存在于 Chrome 使用的 JavaScript 引擎 V8 中,所以在下一次 V8 引擎的更新中,这些方法将变得在 Node.js 中可用。
总之,这些方法代表了 JavaScript 未来的发展方向,它们有望成为标准的一部分,并且已经开始在现代浏览器和 JavaScript 运行时中得到支持。
你可能会好奇为什么我们选择实施了 Object.groupBy 而不是像 Array.prototype.groupBy 这样的数组原型方法。这是因为根据这个提案的说明,曾经有一个库尝试在 Array.prototype 上添加了一个不兼容的 groupBy 方法的补丁。在设计新的 API 时,特别是在网络环境下,保持向后兼容性非常重要。几年前,当试图在 JavaScript 中实现 Array.prototype.flatten 方法时,就曾经发生过类似的事件,这被戏称为 "SmooshGate" 事件。
幸运的是,通过使用静态方法(如 Object.groupBy),我们实际上能够更好地保障未来的可扩展性。当记录和元组提案得到实现时,我们可以向这些对象添加新的方法,以便将数组按不可变记录的方式进行分组。这意味着通过静态方法,我们可以更灵活地扩展 JavaScript 的核心功能,而不会破坏现有的代码或引发兼容性问题。这有助于确保 JavaScript 在不断发展的同时保持向后兼容。
将事物分成一组是我们开发者常常需要做的事情,就好像整理东西一样重要。每周,有大约150万到200万次人们从npm上下载lodash.groupBy这个工具。看到JavaScript为我们提供了更好的方法来做这些事情,让我们的工作变得更加轻松,这真是太好了。
由于文章内容篇幅有限,今天的内容就分享到这里,文章结尾,我想提醒您,文章的创作不易,如果您喜欢我的分享,请别忘了点赞和转发,让更多有需要的人看到。同时,如果您想获取更多前端技术的知识,欢迎关注我,您的支持将是我分享最大的动力。我会持续输出更多内容,敬请期待。