使用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} ];
两种实现写法
- 带判断的传统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);
- 单行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
相关产品推荐
相关产品推荐

