You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用reduce处理同一数组结果不一致的原因及更优写法咨询

按ID分组求和的代码冲突问题及优化方案

问题重现

给定原始数据:

const data = [
  {"id": "One", "number": 100}, 
  {"id": "One", "number": 150}, 
  {"id": "One", "number": 200}, 
  {"id": "Two", "number": 50}, 
  {"id": "Two", "number": 100}, 
  {"id": "Three", "number": 10}, 
  {"id": "Three", "number": 90}
];

两种实现写法

  1. 带判断的传统reduce写法:
let result2 = data.reduce((a, v) => {
  let obj = a.find(i => i.id == v.id);
  if (obj) {
      obj.number += v.number;
  } else {
      a.push(v);
  }
  return a;
}, []);

console.log(result2);
  1. 单行reduce写法:
let result3 = data.reduce((acc, {id, number}) => ({...acc, [id]: {id, number: acc[id] ? acc[id].number + number: number}}), {});
console.log(Object.values(result3));

现象

单独运行任意一种写法都能得到正确的分组求和结果,但同时运行时,第二种写法的结果不符合预期。

问题原因

第一种写法存在副作用:执行a.push(v)时,直接把原data数组中的对象引用推入了结果数组。后续执行obj.number += v.number时,会直接修改原data数组中对应对象的number值。

当两种写法同时运行时,第二种写法读取的number已经是被第一种写法修改后的值,导致重复累加,最终结果错误。比如原data中第一个"One"的number原本是100,第一种写法运行后会被改成450(100+150+200),第二种写法运行时读取这个修改后的值,再和后面的150、200相加,结果自然偏离预期。

修复与优化方案

修复第一种写法的副作用

修改第一种写法,推入新对象而非原对象引用,避免修改原data数组:

let result2 = data.reduce((a, v) => {
  let obj = a.find(i => i.id == v.id);
  if (obj) {
      obj.number += v.number;
  } else {
      a.push({...v}); // 创建新对象,不引用原数据
  }
  return a;
}, []);

更优雅的单行实现

可以用可选链简化判断,同时保证不依赖原数据的状态:

const result = Object.values(
  data.reduce((acc, {id, number}) => ({
    ...acc,
    [id]: {id, number: (acc[id]?.number || 0) + number}
  }), {})
);

或者用Object.entries结合map的写法,可读性更强:

const result = Object.entries(
  data.reduce((acc, {id, number}) => ({...acc, [id]: (acc[id] || 0) + number}), {})
).map(([id, number]) => ({id, number}));

这两种写法都不会修改原数据,多次运行或和其他代码共存时也不会出现冲突。

内容的提问来源于stack exchange,提问作者flyingfox

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.13 17:20:37