
本文详解如何使用 JavaScript 的 reduce() 方法对具有相同关键字段(如 name、date、img、cost)的对象数组进行分组聚合,同时对数值字段(如 paid)求和,并将关联的用户名收集为嵌套数组。
本文详解如何使用 JavaScript 的 `reduce()` 方法对具有相同关键字段(如 name、date、img、cost)的对象数组进行分组聚合,同时对数值字段(如 paid)求和,并将关联的用户名收集为嵌套数组。
在实际业务开发中(如订单汇总、商品采购统计),我们常需将结构相似但来源分散的数据按业务维度(如商品名、日期、价格)归并,并对数值型字段(如已付金额 paid)求和,同时保留关联的明细信息(如多个买家用户名)。原始数据通常是扁平的一维对象数组,而目标格式是“主记录 + 子项集合”的多维结构。
以下是一个典型场景:
你从数据库获取一组购买记录,每条记录包含商品名、日期、图片路径、成本价、已付金额和买家用户名。当多个用户购买同一商品(各项属性完全一致)时,需将其合并为一条记录,其中 paid 字段为所有匹配项之和,username 字段则升维为 usernames: [{username: "Adam"}, {username: "Alvin"}] 形式的数组。
✅ 正确实现:使用 reduce() 进行键匹配与聚合
const array = [
{
name: 'Iphone',
date: '01.01.2024',
img: 'img/iphone.png',
cost: 2500,
paid: 500,
username: "Joe",
},
{
name: 'Samsung',
date: '01.01.2024',
img: 'img/samsung.png',
cost: 2000,
paid: 200,
username: "Adam",
},
{
name: 'Samsung',
date: '01.01.2024',
img: 'img/samsung.png',
cost: 2000,
paid: 100,
username: "Alvin",
}
];
const merged = array.reduce((acc, curr) => {
// 定义唯一分组键:所有用于判定“是否为同一商品”的字段
const keyFields = ['name', 'date', 'img', 'cost'];
const isMatch = acc.some(item =>
keyFields.every(field => item[field] === curr[field])
);
if (isMatch) {
// 找到已有分组 → 累加 paid,追加 usernames
const target = acc.find(item =>
keyFields.every(field => item[field] === curr[field])
);
target.paid += curr.paid;
target.usernames.push({ username: curr.username });
} else {
// 新建分组 → 初始化 paid 和 usernames
acc.push({
...curr,
paid: curr.paid, // 显式保留初始 paid 值(避免引用问题)
usernames: [{ username: curr.username }]
});
// 注意:删除原 username 字段,避免冗余;保留其他字段(name/date/img/cost)
delete acc[acc.length - 1].username;
}
return acc;
}, []);
console.log(merged);
// 输出:
// [
// {
// name: 'Iphone',
// date: '01.01.2024',
// img: 'img/iphone.png',
// cost: 2500,
// paid: 500,
// usernames: [{ username: 'Joe' }]
// },
// {
// name: 'Samsung',
// date: '01.01.2024',
// img: 'img/samsung.png',
// cost: 2000,
// paid: 300,
// usernames: [{ username: 'Adam' }, { username: 'Alvin' }]
// }
// ]⚠️ 关键注意事项
-
分组键需严谨:本例以
name + date + img + cost四字段联合判等。若业务中仅需按name和date分组,请同步修改keyFields数组。 -
避免浅拷贝陷阱:直接
...curr展开会复制所有属性(含username),因此需显式delete原始username字段,确保输出结构纯净。 -
数值累加安全:
target.paid += curr.paid前应确保target.paid已初始化为数字(首次创建时已赋值curr.paid,故安全)。 -
性能提示:对于超大数据集(>10,000 条),
find()/some()的 O(n²) 时间复杂度可能成为瓶颈,此时建议改用Map预构建哈希索引,将时间复杂度优化至 O(n)。
✅ 进阶建议:使用 Map 提升大规模数据性能
const keyToString = (item) => `${item.name}|${item.date}|${item.img}|${item.cost}`;
const map = new Map();
array.forEach(curr => {
const key = keyToString(curr);
if (map.has(key)) {
const existing = map.get(key);
existing.paid += curr.paid;
existing.usernames.push({ username: curr.username });
} else {
map.set(key, {
...curr,
usernames: [{ username: curr.username }],
paid: curr.paid
});
delete map.get(key).username;
}
});
const mergedWithMap = Array.from(map.values());无论采用 reduce 还是 Map,核心逻辑一致:识别重复维度 → 合并数值字段 → 收集明细字段。掌握这一模式,可灵活适配各类聚合需求(如统计数量、取最大值、拼接字符串等)。
总结:JavaScript 数组的“多维合并”本质是分组 + 聚合。reduce() 是最直观的函数式解法,而 Map 则是高性能场景下的优选方案。根据数据规模与可维护性要求合理选择即可。
立即学习“Java免费学习笔记(深入)”;


















