Why does a js map on an array modify the original array?

Javascriptnode.jsDictionaryFunctor

Javascript Problem Overview


I'm quite confused by the behavior of map().

I have an array of objects like this:

const products = [{
    ...,
    'productType' = 'premium',
    ...
}, ...]

And I'm passing this array to a function that should return the same array but with all product made free:

[{    ...,    'productType' = 'free',    ...}, ...]

The function is:

const freeProduct = function(products){
    return products.map(x => x.productType = "free")
}

Which returns the following array:

["free", "free", ...]

So I rewrote my function to be:

const freeProduct = function(products){
    return products.map(x => {x.productType = "free"; return x})
}

Which returns the array as intended.

BUT ! And that's the moment where I loose my mind, in both cases my original products array is modified.

Documentation around map() says that it shouldn't ( https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map ).

I even tried to create a clone of my array turning my function into this:

const freeProduct = function(products){
    p = products.splice()
    return p.map(x => {x.productType = "free"; return x})
}

But I still get the same result (which starts to drive me crazy).

I would be very thankful to anyone who can explain me what I'm doing wrong!

Thank you.

Javascript Solutions


Solution 1 - Javascript

You're not modifying your original array. You're modifying the objects in the array. If you want to avoid mutating the objects in your array, you can use Object.assign to create a new object with the original's properties plus any changes you need:

const freeProduct = function(products) {
  return products.map(x => {
    return Object.assign({}, x, {
      productType: "free"
    });
  });
};

2018 Edit:

In most browsers you can now use the object spread syntax instead of Object.assign to accomplish this:

const freeProduct = function(products) {
  return products.map(x => {
    return {
      ...x,
      productType: "free"
    };
  });
};

Solution 2 - Javascript

To elaborate on SimpleJ's answer - if you were to === the two arrays, you would find that they would not be equal (not same address in memory) confirming that the mapped array is in fact a new array. The issue is that you're returning a new array, that is full of references to the SAME objects in the original array (it's not returning new object literals, it's returning references to the same object). So you need to be creating new objects that are copies of the old objects - ie, w/ the Object.assign example given by SimpleJ.

Solution 3 - Javascript

Unfortunately, whether the spread operator nor the object assign operator does a deep copy.... You need to use a lodash like function to get areal copy not just a reference copy.

const util = require('util');
const print = (...val) => {
    console.log(util.inspect(val, false, null, false /* enable colors */));
};
const _ = require('lodash');

const obj1 =     {foo:{bar:[{foo:3}]}};
const obj2 =     {foo:{bar:[{foo:3}]}};

const array = [obj1, obj2];

const objAssignCopy = x => { return Object.assign({}, x, {})};
const spreadCopy = x => { return {...x}};
const _Copy = x => _.cloneDeep(x);

const map1 = array.map(objAssignCopy);
const map2 = array.map(spreadCopy);
const map3 = array.map(_Copy);

print('map1', map1);
print('map2', map2);
print('map3', map3);
obj2.foo.bar[0].foo = "foobar";
print('map1 after manipulation of obj2', map1); // value changed 
print('map2 after manipulation of obj2', map2); // value changed
print('map3 after manipulation of obj2', map3); // value hasn't changed!

Solution 4 - Javascript

Array Iterator Array.map() creates the new array with the same number of elements or does not change the original array. There might be the problem with referencing if there is object inside the array as it copies the same reference, so, when you are making any changes on the property of the object it will change the original value of the element which holds the same reference.

The solution would be to copy the object, well, array.Splice() and [...array](spread Operator) would not help in this case, you can use JavaScript Utility library like Loadash or just use below mention code:

const newList = JSON.parse(JSON.stringify(orinalArr))

Solution 5 - Javascript

Array Destructuring assignment can be used to clone the object.

const freeProduct = function(products){
    p = products.splice()
    return p.map(({...x}) => {x.productType = "free"; return x})
}

This method will not modify the original object.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionEdwin JoassartView Question on Stackoverflow
Solution 1 - JavascriptSimpleJView Answer on Stackoverflow
Solution 2 - Javascriptuser5004821View Answer on Stackoverflow
Solution 3 - JavascriptPatrick W.View Answer on Stackoverflow
Solution 4 - JavascriptKushal AtreyaView Answer on Stackoverflow
Solution 5 - JavascriptPranaView Answer on Stackoverflow