用 reduce() 实现“列转行”需明确分组键、聚合目标和初始结构,以单次遍历完成分组或带统计的嵌套转换,避免多次 filter/map,性能更优。

用 reduce() 实现“列转行”(即按某个字段分组聚合,把数组从扁平结构转为嵌套结构),关键不是堆砌逻辑,而是明确分组键、聚合目标和初始结构。性能优势来自单次遍历 + 原生方法优化,避免多次 filter/map。
明确“列转行”的实际业务含义
所谓“列转行”,在前端常见场景中通常指:
- 把按时间/类别/用户等维度扁平排列的对象数组,
- 转成以该维度为 key、对应子数组为 value 的对象(如:{ '2024-01': [...], '2024-02': [...] });
- 或进一步聚合为带统计字段的对象(如:{ 'A': { count: 5, total: 1200 } })。
这不是 SQL 的 pivot,而是 JavaScript 中的“分组归集”。reduce() 天然适合——它逐项累积状态,无需预扫描或重复查找。
基础分组:单字段键 + 子数组值
这是最常用形态,例如按 status 或 category 分组:
const list = [
{ id: 1, name: '订单1', status: 'pending' },
{ id: 2, name: '订单2', status: 'done' },
{ id: 3, name: '订单3', status: 'pending' }
];
<p>const grouped = list.reduce((acc, item) => {
const key = item.status;
if (!acc[key]) acc[key] = [];
acc[key].push(item);
return acc;
}, {});
// → { pending: [{id:1,...}, {id:3,...}], done: [{id:2,...}] }
- 初始化 accumulator 为
{},而非new Map()—— 对象属性访问在 V8 中已高度优化,且更符合 JSON 序列化需求 - 避免使用
acc[key] = [...(acc[key] || []), item]—— 每次展开+新建数组会产生大量中间对象,降低性能 - 用
if (!acc[key]) acc[key] = []配合.push()是最轻量的追加方式
带聚合计算的进阶重构
当需要统计、求和、去重等时,可在 reduce 中同步计算,避免后续遍历:
const sales = [
{ region: 'North', amount: 1200, product: 'A' },
{ region: 'South', amount: 800, product: 'B' },
{ region: 'North', amount: 950, product: 'A' }
];
<p>const summary = sales.reduce((acc, item) => {
const { region, amount, product } = item;
if (!acc[region]) {
acc[region] = { total: 0, count: 0, products: new Set() };
}
acc[region].total += amount;
acc[region].count += 1;
acc[region].products.add(product);
return acc;
}, {});</p><p>// → { North: { total: 2150, count: 2, products: Set {'A'} }, ... }
- 每个分组值初始化为一个“聚合单元”,包含所需字段(数字累加、计数器、Set 去重等)
- 用
Set替代数组去重,插入和查重都是 O(1),比!arr.includes(x)高效得多 - 所有计算在一次 reduce 中完成,无额外 map/filter/filter 链式调用
处理多级分组或动态键路径
当分组依据是嵌套字段(如 user.profile.city)或需运行时确定键名时,可封装提取逻辑:
const getKey = (item, path) =>
path.split('.').reduce((obj, key) => obj?.[key], item);
<p>const byCity = list.reduce((acc, item) => {
const city = getKey(item, 'user.profile.city');
if (city && !acc[city]) acc[city] = [];
if (city) acc[city].push(item);
return acc;
}, {});
- 将路径解析抽离为纯函数,便于复用和测试
- 加入
obj?.[key]可选链,防止因数据不全报错 - 对空/undefined 键做守卫(
if (city)),避免生成无效分组
不复杂但容易忽略:reduce 的初始值类型必须与 accumulator 期望结构一致;键名尽量保持字符串化(避免 Symbol 或对象作 key);若需最终输出为数组(如 Object.entries()),放在 reduce 之后做,不要在 reduce 内部反复转换。


















