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

使用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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.29 10:27:30