【问题标题】:Javascript .map() on copied array复制数组上的 Javascript .map()
【发布时间】:2018-12-29 02:25:20
【问题描述】:

我注意到调用 .map() 而不将其分配给变量会使其返回整个数组,而不仅仅是更改的属性:

const employees = [{
    name: "John Doe",
    age: 41,
    occupation: "NYPD",
    killCount: 32,
  },
  {
    name: "Sarah Smith",
    age: 26,
    occupation: "LAPD",
    killCount: 12,
  },
  {
    name: "Robert Downey Jr.",
    age: 48,
    occupation: "Iron Man",
    killCount: 653,
  },

]

const workers = employees.concat();

workers.map(employee =>
  employee.occupation == "Iron Man" ? employee.occupation = "Philantropist" : employee.occupation
);

console.log(employees);

但考虑到 .concat() 创建了原始数组的副本并将其分配给工作人员,为什么员工也会发生变异?

【问题讨论】:

  • 工人可能是原始雇员的副本,但里面的对象是相同的引用。
  • 如果您需要深度复制某些内容,请致电 npm:clone
  • “我注意到调用 .map() 而不将其分配给变量会使其返回整个数组,而不仅仅是更改的属性:” Array.prototype.map 总是返回数组。如何使用该返回值并不重要。你的说法对我来说没有多大意义。

标签: javascript arrays function ecmascript-6


【解决方案1】:

发生这种情况是因为数组中的对象仍被相同的指针引用。 (您的数组仍然指的是内存中的相同对象)。此外,Array.prototype.map() 总是返回一个数组,并且它的结果应该分配给一个变量,因为它不进行 in-place 映射。当您在 map 方法中更改对象的属性时,您应该考虑改用 .forEach() 来修改复制的雇员数组中对象的属性。要制作员工数组的副本,您可以使用以下命令:

const workers = JSON.parse(JSON.stringify(employees));

请看下面的例子:

const employees = [
  {
    name: "John Doe",
    age: 41,
    occupation: "NYPD",
    killCount: 32,
  },
  {
    name: "Sarah Smith",
    age: 26,
    occupation: "LAPD",
    killCount: 12,
  },
  {
    name: "Robert Downey Jr.",
    age: 48,
    occupation: "Iron Man",
    killCount: 653,
  },

]


const workers = JSON.parse(JSON.stringify(employees));
workers.forEach(emp => {
  if(emp.occupation == "Iron Man") emp.occupation = "Philantropist";
});

console.log("--Employees--")
console.log(employees);
console.log("\n--Workers--");
console.log(workers);
  • 注意:如果您的数据中包含任何方法,则需要使用其他方法进行深度复制

【讨论】:

  • @JonasWilms 真的!谢谢,我已经修改了答案
  • 现在workers 仅包含occupations ...也许推荐forEachfor..of 代替?
  • @JonasWilms forEach 会是更好的方法,我再次更新了我的答案
  • 因此,与其创建一个复制了包含对象(作为数组)和实际数组值(作为文字对象)的副本,它只会为包含数组创建一个新引用吗?在这种情况下,如果数组值是原始类型,我提供的示例不会有问题,对吧?例如,员工 = [“John Doe”、“sarah smith”、“Robert Downey Jr.”]
  • @Abbadiah 是的,没错。如果您使用原始类型(例如字符串),则不会遇到同样的问题。
【解决方案2】:

问题分析

workers = workers.map(employee => 
  employee.occupation == "Iron Man" ? (employee.occupation = "Philantropist", employee) : (employee.occupation, employee)
);

[...] 为什么员工也会变异?

array.map() 使用array 中的每个 元素调用传递的函数,并返回一个包含该函数返回值的新数组。

你的函数只返回表达式的结果

element.baz = condition ? foo : bar;

根据情况,会

  • 评估为foobar
  • 将该结果分配给baz
  • 返回结果

进一步(expression1, expression2) 将评估这两个表达式并返回expression2(参见comma operator)。

因此,尽管在这两种情况下都返回 employee,但您使用左侧表达式(表达式 1)修改了原始对象。

可能的解决方案

您可能想使用Object.assign() 创建一个新对象

array.map((employee) => Object.assign({ }, employee))

而不是使用那个array.concat()“技巧”。使用该映射,您不仅可以创建一个新数组,还可以创建具有复制属性的新对象。虽然这不会像{ foo: { ... } } 这样“深度复制”嵌套对象 - 通过属性foo 可访问的对象仍将与原始引用相同。在这种情况下,您可能需要查看其他答案中提到的深度复制模块。

【讨论】:

    【解决方案3】:

    数组引用发生变化,但复制的数组仍引用原始数组中的原始对象。因此,数组中对象的任何更改都会反映在数组的所有副本中。除非您对数组进行深层复制,否则内部对象中的一些更改可能会反映在每个副本中

    What is the difference between a deep copy and a shallow copy?

    可以通过多种方式制作深拷贝。这篇文章专门讨论了:What is the most efficient way to deep clone an object in JavaScript?

    【讨论】:

      【解决方案4】:

      map 从回调返回的值构建一个新数组,caj 很容易用于克隆数组中的对象:

       const workers = employees.map(employee => ({
           ...employee, // take everything from employee
           occupation: employee.ocupation == "Iron Man" ? "Philantropist" : employee.occupation
       }));
      

      或者您可以对数组进行深度克隆,然后使用简单的for 对其进行变异:

       const workers = JSON.parse(JSON.stringify(workers));
       for(const worker of workers)
         if(worker.ocupation == "Iron Man")
          worker.ocupation = "Philantropist";
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2017-02-16
        • 1970-01-01
        • 2017-10-02
        • 2013-03-21
        • 2011-10-29
        • 1970-01-01
        • 2019-07-28
        • 1970-01-01
        相关资源
        最近更新 更多