JavaScript 数组分组新标准:Object.groupBy 与 Map.groupBy 详解
JavaScript 原生引入了 Object.groupBy 和 Map.groupBy 方法,简化了数组分组操作。Object.groupBy 返回空原型对象,键自动转为字符串;Map.groupBy 保留 Map 特性,支持任意类型键。本文对比旧版 lodash 实现,解析浏览器兼容性及静态方法设计原因,助您掌握最新分组标准。
对数组元素进行分组,这或许是开发者频繁执行的操作。以往,我们要么手动构建分组逻辑,要么依赖 lodash 库提供的 groupBy 工具。
值得庆幸的是,JavaScript 原生引入了分组功能,彻底免去了上述繁琐步骤。Object.groupBy 与 Map.groupBy 这两个新 API 的加入,不仅简化了分组流程,还减少了对外部库或自定义函数的依赖。
传统实现方式
设想有一个包含人员信息的对象数组,目标是根据年龄字段进行归类。一种常见的做法是借助 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 API,实现上述逻辑变得异常简洁:
const peopleByAge = Object.groupBy(people, (person) => person.age);
代码大幅精简,但仍有若干细节需留意。
Object.groupBy 返回的对象拥有空原型(null prototype)。这意味着该对象不会继承 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类型的值。若返回其他类型,引擎会将其强制转换为字符串。
在前述示例中,回调函数返回的是数字类型的age,但最终结果中的键被强制转换为了字符串。不过,由于方括号访问符在获取属性时也会执行字符串转换,因此使用数字键依然可以正确访问属性。
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 实例方法的能力。同时,这也允许回调函数返回任意类型的值作为键。
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 方法属于 TC39 提案的第三阶段。鉴于其标准化前景广阔,各大浏览器厂商已开始提供实验性支持。
Chrome 浏览器 117 版本已原生支持这两种方法,Firefox 浏览器 119 版本亦跟进支持。Safari 虽以不同名称实现了相关功能,但预计不久后也将更新以保持一致。鉴于 Chrome 基于 V8 引擎实现了这些方法,随着 V8 的后续更新,Node.js 环境也将随之支持。
静态方法的设计考量
读者或许会疑惑,为何采用 Object.groupBy 而非 Array.prototype.groupBy 的形式?根据提案背景,此前有库通过不兼容的 groupBy 方法对 Array.prototype 进行了猴子补丁。在定义新 API 时,向后兼容性至关重要。几年前在尝试实现 Array.prototype.flatten 时发生的“SmooshGate”事件,便深刻揭示了这一问题的严重性。
幸运的是,采用静态方法似乎更利于未来的功能扩展。例如,当 Record 和 Tuples 提案落地时,我们可以引入一个 Record.groupBy 方法,用于将数组分组为不可变记录。
总结
数据分组无疑是开发者日常工作中的高频需求。目前,每周从 npm 下载 lodash.groupBy 的次数维持在 150 万至 200 万次之间。JavaScript 原生填补这一空白,无疑极大地提升了开发效率。
建议立即升级至 Chrome 117 版本,亲身体验这些新特性。
至此,关于 JavaScript 中新数组分组方法的详解介绍完毕。如需了解更多 JavaScript 数组分组相关内容,欢迎查阅本站历史文章或继续浏览后续教程,感谢大家的支持!
JavaScript入门书籍避坑:这些书不适合初学者
2026-11-05 16:31:15
JS使用Math.round实现保留两位小数的原理与代码
2026-11-05 15:35:55
JavaScript 原生 Date 对象怎么实现日期加减
2026-11-05 14:30:00
JavaScript函数定义的三种方式:声明、表达式与箭头函数
2026-11-05 13:50:55
JavaScript中escape、encodeURI和encodeURIComponent对空格的处理差异
2026-11-05 12:47:23
