2

I have noticed that invoking .map() without assigning it to a variable makes it return the whole array instead of only the changed properties:

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);

But considering that .concat() created a copy of the original array and assigned it into workers, why does employees get mutated as well?

connexo
  • 53,704
  • 14
  • 91
  • 128
Abbadiah
  • 171
  • 1
  • 3
  • 10
  • 2
    Workers may be a copy of original employees, yet the objects inside are the same references. – connexo Dec 29 '18 at 02:29
  • Call [npm:clone](https://www.npmjs.com/package/clone) if you need to deep copy something. – Paul Dec 29 '18 at 02:34
  • 1
    Possible duplicate of [why a js map on an array modify the original array?](https://stackoverflow.com/questions/35922429/why-a-js-map-on-an-array-modify-the-original-array) – Heretic Monkey Dec 29 '18 at 04:05
  • 1
    *"I have noticed that invoking .map() without assigning it to a variable makes it return the whole array instead of only the changed properties:"* `Array.prototype.map` always returns an array. It doesn't matter how you use that return value. The statement of yours doesn't make much sense to me. – Felix Kling Dec 29 '18 at 08:12

4 Answers4

6

This is happening because your objects within the array are still being referenced by same pointers. (your array is still referring to the same objects in memory). Also, Array.prototype.map() always returns an array and it's result should be assigned to a variable as it doesn't do in-place mapping. As you are changing the object's properties within your map method, you should consider using .forEach() instead, to modify the properties of the object within the copied employees array. To make a copy of your employees array you can use the following:

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

See example below:

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);
  • Note: If your data has any methods within it you need to use another method to deep copy
Nick Parsons
  • 45,728
  • 6
  • 46
  • 64
  • @JonasWilms true! Thanks, I've amended my answer – Nick Parsons Dec 29 '18 at 12:47
  • 1
    Now `workers` contains `occupations` only ... maybe recommend `forEach` or `for..of` instead? – Jonas Wilms Dec 29 '18 at 12:49
  • @JonasWilms `forEach` would be a better approach, I've updated my answer again – Nick Parsons Dec 29 '18 at 13:16
  • So rather than creating a copy where you have both the containing object (being array) and the actual array values (being literal objects) copied, it only creates a new reference for the containing array? In this case, the example I provided would not have a problem if the array values would have been primitive types, right? For example, employees = ["John Doe", "sarah smith", "Robert Downey Jr."] – Abbadiah Dec 29 '18 at 17:20
  • @Abbadiah yes, that's right. If you use primitive types such as strings you won't have the same issue. – Nick Parsons Dec 30 '18 at 01:22
3

Problem analysis

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

[...] why does employees get mutated as well?

array.map() calls the passed function with each element from the array and returns a new array containing values returned by that function.

Your function just returns the result of the expression

element.baz = condition ? foo : bar;

which, depending on the condition, will

  • evaluate to foo or bar
  • assign that result to baz and
  • return that result

Further (expression1, expression2) will evaluate both expressions and return expression2 (see the comma operator).

So, although you return employee in both cases, you modify the original object with the left expression (expression 1).

Possible solution

You might want to create a new object using Object.assign()

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

instead of using that array.concat() "trick". Using that mapping, you not only create a new array but also new objects with their attributes copied. Though this would not "deep copy" nested objects like { foo: { ... } } - the object accessible via the property foo would still be the same reference as the original. In such cases you might want to take a look on deep copying modules mentioned in the other answers.

Nick Parsons
  • 45,728
  • 6
  • 46
  • 64
try-catch-finally
  • 7,436
  • 6
  • 46
  • 67
0

The array references change but the copied array still reference the original objects in the original array. So any change in the objects in the array are reflected across all copies of the array. Unless you do a deep copy of the array, there is a chance that the some changes in the inner objects get reflected across each copy

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

Deep copies can be made in several ways. This post discusses specifically that: What is the most efficient way to deep clone an object in JavaScript?

aaruja
  • 371
  • 2
  • 12
0

map builds up a new array from the values returned from the callback, which caj easily be used to clone the objects in the array:

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

Or you could deep clone the array, then mutate it with a simple for:

 const workers = JSON.parse(JSON.stringify(workers));
 for(const worker of workers)
   if(worker.ocupation == "Iron Man")
    worker.ocupation = "Philantropist";
Jonas Wilms
  • 132,000
  • 20
  • 149
  • 151