7

Why would the map method mutate the original array when its initial purpose is to create a new array ?

I have an array of object which I pass to a pure function which in turn maps the given array and return a new one. Then I notice that the original array was also changed.. I understand the concept that Object in Js are passed by reference and all but still cant quite grab why would the implementation of map would mutate the original array, kinda beats the purpose IMO.

var initialArray = [ { name: 'one' }, { name: 'two' }, { name: 'three'} ];

function doSomething(array) {

  // lodash
  // return _.map(array, (item) => _.assign(item, {isSelected: true}));  

  // vanilla
  return array.map(function(item) {
    item['isSelected'] = true;
    return item
  });

}

var changedArray = doSomething(initialArray);

console.log('initialArray', initialArray); // [{ name: 'one', isSelected: true }, ...]
console.log('changedArray', changedArray); // [{ name: 'one', isSelected: true }, ...]
console.log(initialArray === changedArray); // false

First Id like to understand why this happens ?

Second Id like to understand how would one map an array without changing the original one ? (ie. doing ._cloneDeep each time before map feels wrong)

Thanks in advance !

Edit

Ok so from what I understand this is how things just are. I think I might have had higher expectation for some reason, but it is explainable in Js so at least there is some consistency in place.

The most elegant solution I can think of for creating a new array with new members is

return _.map(array, (item) => _.assign({}, ...item, {isSelected: true}));   
2
  • 1
    A comment about the last line: console.log( initialArray.... This almost never works if you have to compare arrays (or for that matter, Objects) in JavaScript. Have a look at the fiddle: jsfiddle.net/76wyLxj5 Commented Jan 11, 2016 at 8:15
  • I can't post it as an answer as it'll be off-topic, but here is a great answer for it: stackoverflow.com/questions/7837456/… Commented Jan 11, 2016 at 8:32

4 Answers 4

16

.map will create a new array, but the objects inside the array is still referenced.

so when you make changes in the object item inside .map function, it is referencing the original object in the input array.

one way to fix it is to clone the each object , before you modify it

var initialArray = [ { name: 'one' }, { name: 'two' }, { name: 'three'} ];

function clone(obj) {
    if (null == obj || "object" != typeof obj) return obj;
    var copy = obj.constructor();
    for (var attr in obj) {
        if (obj.hasOwnProperty(attr)) copy[attr] = obj[attr];
    }
    return copy;
}

function doSomething(array) {

  // lodash
  // return _.map(array, (item) => _.assign(item, {isSelected: true}));  

  // vanilla
  return array.map(function(item) {
    var copy = clone(item);
    copy['isSelected'] = true;
    return copy;
  });

}

var changedArray = doSomething(initialArray);

console.log('initialArray', initialArray); // [{ name: 'one'}, ...]
console.log('changedArray', changedArray); // [{ name: 'one', isSelected: true }, ...]
console.log(initialArray === changedArray); // false

Credit : clone function is copied from this post

Sign up to request clarification or add additional context in comments.

3 Comments

You can use array.slice() instead of that clone function, but yeah.
Or use any of the options here (see what best fits your need performance wise)
One could also make a copy of the original property by using the Object.assign({}, propertyOrTheWholeObjectToBeCopied).
3

If you want to just solve for OP's example then you can spread the item object into a new object returned from Array.map().

var initialArray = [ { name: 'one' }, { name: 'two' }, { name: 'three'} ];

function doSomething(array) {

  // lodash
  // return _.map(array, (item) => _.assign(item, {isSelected: true}));  

  // vanilla
  return array.map(function(item) {
    return {
      ...item,
      isSelected: true
    }
  });

}

var changedArray = doSomething(initialArray);

console.log('initialArray', initialArray); // initialArray [ { name: 'one' }, { name: 'two' }, { name: 'three' } ] 
console.log('changedArray', changedArray); // changedArray [ { name: 'one', isSelected: true }, { name: 'two', isSelected: true }, { name: 'three', isSelected: true } ] 
console.log(initialArray === changedArray); // false

Note: This solution wouldn't allow you to dereference any objects nested beyond level one without also using the spread operator.

More information on the spread operator can be found here.

Comments

2

your modifying the object that get's passed by reference to the map function, not the array that get's mapped. Both the changedArray and the initialArray contain the same object.

var initialArray = [ { name: 'one' }, { name: 'two' }, { name: 'three'} ];
var initialArray2 = [ { name: 'one' }, { name: 'two' }, { name: 'three'} ];

function doSomething(array) {
  // vanilla
  return array.map(function(item) {
    item['isSelected'] = true;
    return item
  });

}

function doSomethingElse(array){
  return array.map(function( item ){
    // return a new object don't change the initial one
    return { name: item.name, isSelected: true };
  });
}

var changedArray = doSomething(initialArray),
    differentObjectsInArray = doSomethingElse( initialArray2 );

console.assert( initialArray !== changedArray, 'both arrays are different' );
console.assert( initialArray[0] !== changedArray[0], 'both arrays are referencing different objects' );
console.assert( initialArray2[0] !== differentObjectsInArray[0], 'both arrays are referencing different objects' );
console.log('initialArray', initialArray );
console.log('initialArray2', initialArray2 );
console.log('differentObjectsInArray', differentObjectsInArray );
<script src="http://codepen.io/synthet1c/pen/WrQapG.js"></script>

Comments

0
 var targetArray=JSON.parse(JSON.stringify(souceArray));

1 Comment

While this code may resolve the OP's issue, it is best to include an explanation as to how your code addresses the OP's issue. In this way, future visitors can learn from your post, and apply it to their own code. SO is not a coding service, but a resource for knowledge. Also, high quality, complete answers are more likely to be upvoted. These features, along with the requirement that all posts are self-contained, are some of the strengths of SO as a platform, that differentiates it from forums. You can edit to add additional info &/or to supplement your explanations with source documentation.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.