JavaScript Map.groupBy按条件分组的集合操作
处理大量杂乱数据时,经常需要将具有相同特征的数据归纳到一起。传统的 for 循环或 reduce 方法代码冗长且难以阅读。Map.groupBy 提供了一种声明式、简洁的方式来完成这一任务,它可以根据你指定的回调函数规则,自动将数组元素归类到一个新的 Map 对象中。
1. 理解基本逻辑
在使用前,先明确 Map.groupBy 的核心运作流程。它接收一个可迭代对象(通常是数组)和一个回调函数,遍历每一项,根据回调函数返回的“键值”将数据放入对应的“桶”中。
以下是数据流转的示意图:
2. 基础分组操作
以最简单的数字分组为例,将一组数字区分为“奇数”和“偶数”。
- 定义一个包含数字的数组。
- 调用
Map.groupBy方法,传入数组和箭头函数。 - 编写判断逻辑:若数字除以 2 余数为 0,则标记为
even,否则为odd。
const numbers = [1, 2, 3, 4, 5, 6];
const result = Map.groupBy(numbers, (num) => {
if (num % 2 === 0) {
return 'even';
} else {
return 'odd';
}
});
console.log(result);
执行上述代码后,你将得到一个 Map 对象。访问 result.get('odd') 将输出 [1, 3, 5]。
3. 实战场景:商品列表分类
在实际开发中,更常见的场景是根据对象属性(如商品类别)进行分组。
假设有一组商品数据,结构如下:
| 属性名 | 说明 |
|---|---|
name |
商品名称 |
category |
商品分类 |
3.1 准备数据源
复制以下代码创建商品数组:
const products = [
{ name: '苹果', category: '水果' },
{ name: '胡萝卜', category: '蔬菜' },
{ name: '香蕉', category: '水果' },
{ name: '西兰花', category: '蔬菜' },
{ name: '牛肉', category: '肉类' }
];
3.2 执行按类别分组
使用对象解构语法提取 category 属性作为分组的键。
const groupedByCategory = Map.groupBy(products, ({ category }) => category);
// 验证结果
console.log(groupedByCategory.get('水果'));
// 输出: [{ name: '苹果', category: '水果' }, { name: '香蕉', category: '水果' }]
4. 进阶:多条件与复杂逻辑分组
分组逻辑不仅限于单一属性,也可以是基于数值范围、字符串长度或组合逻辑。
4.1 按价格区间分组
假设 products 数组中的每个对象增加了 price 属性。我们要将商品按价格分为“便宜”(< 20)和“昂贵”(≥ 20)两组。
- 更新数据结构,添加价格信息。
- 修改回调函数逻辑,通过
price判断返回的字符串。
const productsWithPrice = [
{ name: '苹果', price: 10 },
{ name: '牛肉', price: 50 },
{ name: '牛奶', price: 15 },
{ name: '龙虾', price: 80 }
];
const groupedByPrice = Map.groupBy(productsWithPrice, ({ price }) => {
return price < 20 ? '便宜' : '昂贵';
});
console.log(groupedByPrice.get('昂贵'));
// 输出: [{ name: '牛肉', price: 50 }, { name: '龙虾', price: 80 }]
4.2 使用对象作为分组键
与 Object.groupBy 不同,Map.groupBy 允许使用对象作为键,而不仅限于字符串。这在处理需要复杂标识符的场景时非常有用。
const key1 = { id: 1 };
const key2 = { id: 2 };
const items = [
{ name: 'Item A', groupKey: key1 },
{ name: 'Item B', groupKey: key2 },
{ name: 'Item C', groupKey: key1 }
];
const groupedByObject = Map.groupBy(items, ({ groupKey }) => groupKey);
// 使用对象 key1 获取数据
console.log(groupedByObject.get(key1));
// 输出: [{ name: 'Item A', groupKey: {...}}, { name: 'Item C', groupKey: {...}}]
5. 处理分组结果
Map.groupBy 返回的是一个 Map 实例。若需将其转换回普通数组或对象以便序列化传输(如发送给后端 API),需进行额外操作。
5.1 遍历 Map
使用 forEach 方法遍历分组结果。
groupedByCategory.forEach((items, category) => {
console.log(`分类: ${category}`);
console.log(`商品数量: ${items.length}`);
});
5.2 转换为普通对象
使用 Object.fromEntries 将 Map 转换为对象。
const objFormat = Object.fromEntries(groupedByCategory);
console.log(objFormat);
// 输出格式: { 水果: [...], 蔬菜: [...], 肉类: [...] }
6. 对比传统方案:reduce vs Map.groupBy
为了凸显 Map.groupBy 的优势,下表对比了传统 reduce 写法与新写法的差异。
| 特性 | reduce 写法 |
Map.groupBy 写法 |
|---|---|---|
| 代码量 | 较多,需手动初始化 Map/对象 | 极少,一行代码搞定 |
| 可读性 | 较差,需深入理解累加器逻辑 | 极高,声明式,直观表达意图 |
| 维护性 | 容易在累加器逻辑中引入 Bug | 逻辑内聚,不易出错 |
| 性能 | 相近 | 相近(底层实现类似) |
以下是使用 reduce 实现相同功能的代码示例:
// 旧写法:冗长且容易出错
const groupedByReduce = products.reduce((acc, product) => {
const key = product.category;
if (!acc.has(key)) {
acc.set(key, []);
}
acc.get(key).push(product);
return acc;
}, new Map());
通过对比可以看出,Map.groupBy 省去了手动初始化容器、检查键是否存在以及手动添加元素的繁琐步骤。
7. 注意事项
- 浏览器兼容性:这是一个较新的特性。检查运行环境(如 Node.js 版本或用户浏览器版本)是否支持。如需支持旧环境,需引入 polyfill 或使用
reduce降级处理。 - 引用相等:当使用对象作为分组键时,必须使用同一个对象的引用来取值,不能通过字面量
{ id: 1 }获取,因为它们在内存中地址不同。 - 非破坏性:该方法不会修改原始数组,而是返回新的
Map。

暂无评论,快来抢沙发吧!