【问题标题】:why doesn't reassigning the parameter element in forEach work为什么不重新分配 forEach 中的参数元素?
【发布时间】:2021-11-23 08:04:20
【问题描述】:

这里不多解释,直接进入正题。

对于以下代码块:

const items = [
  { id: 1, name: 'one' },
  { id: 2, name: 'two' },
];

const changes = {
  name: 'hello'
}

items.forEach((item, i) => {
  item = {
    ...item,
    ...changes
  }
})

console.log(items) // items NOT reassigned with changes

items.forEach((item, i) => {
  items[i] = {
    ...item,
    ...changes
  }
});

console.log(items) // items reassigned with changes

为什么在元素迭代时重新分配值不会改变数组中的对象?

  item = {
    ...item,
    ...changes
  }

但是通过使用索引访问它来改变它会改变数组中的对象吗?

  items2[i] = {
    ...item,
    ...changes
  }

什么是更新数组中对象的最佳方式? items2[i] 理想吗?

【问题讨论】:

  • 这是设计使然。更改您正在迭代的数组可能会导致难以找到和修复的错误。出于您的目的,使用 array.map() 可能会更好。

标签: javascript foreach


【解决方案1】:

对参数重新分配说不!

这是对 JavaScript 等高级语言的一种基本理解。

函数参数是给定值的临时容器。

因此任何“重新分配”都不会改变原始值。

例如看下面的例子。

let importantObject = {
  hello: "world"
}

// We are just reassigning the function parameter
function tryUpdateObjectByParamReassign(parameter) {
  parameter = {
    ...parameter,
    updated: "object"
  }
}


tryUpdateObjectByParamReassign(importantObject)
console.log("When tryUpdateObjectByParamReassign the object is not updated");
console.log(importantObject);

正如您在重新分配参数时看到的那样,原始值不会被触及。甚至还有一个不错的Lint rule,因为这是一个非常容易出现错误的区域。

突变在这里会起作用,但是....

但是,如果您“改变”变量,这将起作用。

let importantObject = {
  hello: "world"
}

// When we mutate the returned object since we are mutating the object the updates will be shown
function tryUpdateObjectByObjectMutation(parameter) {
  parameter["updated"] = "object"
}


tryUpdateObjectByObjectMutation(importantObject)
console.log("When tryUpdateObjectByObjectMutation the object is updated");
console.log(importantObject);

所以回到你的代码 sn-p。在 foreach 循环中发生的是每个数组项的“函数调用”,其中数组项作为参数传入。与上面类似,在这里起作用的是突变。

const items = [
  { id: 1, name: 'one' },
  { id: 2, name: 'two' },
];

const changes = {
  name: 'hello'
}

items.forEach((item, i) => {
  // Object assign just copies an object into another object
  Object.assign(item, changes);
})

console.log(items) 

但是,最好避免突变!

自从this can lead to even more bugs 之后最好不要变异。更好的方法是使用地图并获取全新的对象集合。

const items = [{
    id: 1,
    name: 'one'
  },
  {
    id: 2,
    name: 'two'
  },
];

const changes = {
  name: 'hello'
}

const updatedItems = items.map((item, i) => {
  return {
    ...item,
    ...changes
  }
})

console.log({
  items
})
console.log({
  updatedItems
})

【讨论】:

  • 我想说这应该被接受为正确答案。
【解决方案2】:

正如 forEach 的 MDN 页面所说:

forEach() 对每个数组执行一次 callbackFn 函数 元素;与 map() 或 reduce() 不同,它总是返回值 未定义且不可链接。典型的用例是执行 链末端的副作用。

看看这里:https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/forEach

这意味着尽管您确实为 item 创建了新对象,但它并未作为该数组索引的值返回。与您的第二个示例不同,第一个示例不会更改原始数组,而只是创建新对象并返回未定义。这就是你的数组没有被修改的原因。

【讨论】:

  • 这与 forEach 的工作方式无关。 item = { some object } 只是将局部变量 item 重新分配一个新值,不会影响数组。
  • 我相信这是我写的。在我看来,你只是改写了我写的内容。
  • forEach 是否返回 undefined 与问题无关。 for(let item of items) item = { ...changes } 也会有同样的效果。
【解决方案3】:

为此,我会选择经典的Object.assign

const items = [
  { id: 1, name: 'one' },
  { id: 2, name: 'two' },
];

const changes = {
  name: 'hello'
}

items.forEach( (item) => Object.assign(item,changes) )

console.log(items) 

如果源中的属性具有相同的键,则目标对象中的属性将被覆盖。后期来源的属性会覆盖较早的来源。 https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/assign

【讨论】:

    【解决方案4】:

    您可以采取的另一种方法是使用map 并根据原始数据和更改创建一个新数组:

    const items = [
      { id: 1, name: 'one' },
      { id: 2, name: 'two' },
    ];
    
    const changes = {
      name: 'hello'
    }
    
    const newItems = items.map((item) => {
      ...item,
      ...changes
    })
    
    console.log(newItems);
    

    但是如果你需要修改原始数组,要么通过索引访问元素,要么Object.assign。尝试使用 = 运算符直接分配值不起作用,因为 item 参数被传递给回调按值而不是按引用 - 你是不更新数组指向的对象。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2016-10-25
      • 2018-02-16
      • 2021-11-27
      • 2016-04-08
      • 2013-03-28
      • 1970-01-01
      • 2010-11-12
      • 2023-01-20
      相关资源
      最近更新 更多