JavaScript 中的 Array.prototype.groupBy 与 Object.groupBy 提案实现与差异详解
字数 1584 2025-12-11 11:50:06
JavaScript 中的 Array.prototype.groupBy 与 Object.groupBy 提案实现与差异详解
1. 背景与提案描述
在 JavaScript 开发中,经常需要根据某个属性对数组元素进行分组。传统方式需要手动编写 reduce 或循环,代码冗余。
提案:在 Array.prototype 上添加 groupBy 方法,并在 Object 上添加静态方法 groupBy,实现数组分组功能。
当前状态:已进入 ES2024 标准(Object.groupBy 和 Map.groupBy),Array.prototype.groupBy 被放弃,但理解其演变有助于掌握标准。
2. 核心功能
将数组元素按照特定规则分组,返回一个对象(或 Map),键为分组的键,值为对应分组的元素数组。
示例需求:
const users = [
{ name: 'Alice', age: 25 },
{ name: 'Bob', age: 30 },
{ name: 'Carol', age: 25 }
];
// 按 age 分组 → { 25: [...], 30: [...] }
3. 传统实现方式(手动分组)
3.1 使用 Array.prototype.reduce
const grouped = users.reduce((acc, user) => {
const key = user.age;
if (!acc[key]) acc[key] = [];
acc[key].push(user);
return acc;
}, {});
缺点:需要处理初始化和键的存在性检查,代码不够简洁。
3.2 使用 Map 分组
const map = new Map();
users.forEach(user => {
const key = user.age;
if (!map.has(key)) map.set(key, []);
map.get(key).push(user);
});
缺点:需手动管理 Map 结构。
4. ES2024 标准:Object.groupBy
4.1 语法
Object.groupBy(items, callbackFn)
items:可迭代对象(如数组)。callbackFn:对每个元素执行的函数,返回用于分组的键(字符串或 Symbol)。- 返回值:一个普通对象,键为
callbackFn返回的字符串(或 Symbol),值为对应分组的数组。
4.2 示例
const result = Object.groupBy(users, user => user.age);
// 结果:{ 25: [{...}, {...}], 30: [{...}] }
注意:分组键会被转换为字符串,例如数字 25 会变成 "25"。
4.3 底层机制
- 遍历数组,对每个元素执行回调,得到分组键。
- 将键转换为字符串(通过内部
ToString操作)。 - 在结果对象中创建键(若不存在)并初始化为空数组。
- 将当前元素推入对应数组。
5. ES2024 标准:Map.groupBy
5.1 语法
Map.groupBy(items, callbackFn)
- 参数同
Object.groupBy。 - 返回值:一个 Map 对象,键为
callbackFn的原始返回值(不强制转为字符串),值为对应分组的数组。
5.2 示例
const result = Map.groupBy(users, user => user.age);
// 结果:Map { 25 => [...], 30 => [...] }
优势:分组键保留原始类型(数字、对象等),适合键类型敏感的场景。
6. 两种方法的差异对比
| 特性 | Object.groupBy |
Map.groupBy |
|---|---|---|
| 返回值类型 | 普通对象 | Map 对象 |
| 键类型转换 | 强制转为字符串 | 保留原始类型 |
| 键顺序 | 对象键顺序(整数键优先) | Map 插入顺序 |
| 内存开销 | 较小 | 略大(Map 结构) |
| 适用场景 | 键为字符串或可字符串化 | 键类型复杂(如对象、数字) |
示例:使用对象作为键
const data = [{ id: 1 }, { id: 2 }];
const key1 = { type: 'A' };
const key2 = { type: 'B' };
// Object.groupBy 会失败(对象转为 '[object Object]' 导致键冲突)
// Map.groupBy 可正常使用
const map = Map.groupBy(data, item => item.id === 1 ? key1 : key2);
7. 注意事项与最佳实践
- 键的唯一性:
Object.groupBy中不同值可能因字符串化导致冲突(如true和'true')。 - 不可变数据:分组结果中的数组是原数组的引用,修改分组数组会影响原数据。
- 稀疏数组处理:空位(empty)会被跳过,不执行回调。
- 性能:时间复杂度 O(n),优于手动
reduce(减少重复逻辑)。
8. 兼容性与 Polyfill
- 现代浏览器(Chrome 117+、Firefox 119+)支持。
- Polyfill 实现:
if (!Object.groupBy) {
Object.groupBy = function(items, callbackFn) {
return Array.from(items).reduce((acc, item, index) => {
const key = String(callbackFn.call(this, item, index));
if (!acc[key]) acc[key] = [];
acc[key].push(item);
return acc;
}, {});
};
}
9. 总结
- 使用
Object.groupBy处理键为简单类型的场景,结果更轻量。 - 使用
Map.groupBy处理键类型复杂或需保留键原始类型的场景。 - 提案演进体现了 JavaScript 对数据分组需求的标准化,减少样板代码,提升可读性。