使用reduce无副作用地累加嵌套对象的值
如何无副作用地累加嵌套食物对象中的营养数值
你遇到的问题其实是JavaScript里引用类型的典型坑——对象是按引用传递的,你的代码直接把原始nutrition数组里的对象塞进了累加器,后续修改数值时自然会影响原数据。
问题根源
当你执行acc.push(x)时,并没有创建新的营养对象,而是把原数据里的对象引用直接添加到了累加器数组。之后修改current.val,本质上是在修改原始数组中对应对象的属性,这就产生了意外的副作用。
无副作用的解决方案
我们需要确保所有操作都基于新创建的对象,而不是复用原始对象的引用。这里有两种靠谱的实现方式:
方式一:基于数组累加器,创建对象副本
const foods = [ { id: 'Potato', nutrition: [ { id: 'PROT', val: 4 }, { id: 'SUGAR', val: 1 } ] }, { id: 'Banana', nutrition: [ { id: 'FIB', val: 10 }, { id: 'SUGAR', val: 4 } ] } ]; const summedNutrition = foods.reduce((acc, { nutrition }) => { nutrition.forEach((x) => { const existingItem = acc.find(item => item.id === x.id); if (existingItem) { // 替换为新对象,避免修改原引用 const updatedItem = { ...existingItem, val: existingItem.val + x.val }; acc.splice(acc.indexOf(existingItem), 1, updatedItem); } else { // 创建原营养项的浅副本,不直接引用原对象 acc.push({ ...x }); } }); return acc; }, []);
方式二:用对象做中间映射(更高效)
如果数据量较大,find操作的O(n)开销会比较明显,用对象做中间映射可以把查找复杂度降到O(1):
const summedNutrition = Object.values( foods.reduce((acc, { nutrition }) => { nutrition.forEach(({ id, val }) => { if (acc[id]) { // 这里直接修改的是我们自己创建的对象,和原数据无关 acc[id].val += val; } else { // 基于原始值创建全新对象 acc[id] = { id, val }; } }); return acc; }, {}) );
为什么这两种写法没有副作用?
两种方案都确保了不直接引用原始数组中的任何对象:要么通过扩展运算符{ ...x }创建原对象的浅副本,要么直接用原始的id和val值创建全新的对象。累加过程中所有的修改都只针对我们自己创建的新对象,原始数组的数据完全不会被改动。
另外提一句:原代码里用map其实语义不太对,map的作用是返回一个新数组,而你只是需要遍历执行操作,用forEach会更清晰。
内容的提问来源于stack exchange,提问作者howtopythonpls
相关产品推荐
相关产品推荐

