1

var arr = [{a: "one", b: "two"}];
/* in real code I have actual filter condition, but the filtered result
share some common properties with value */
var res = {
  arr1: arr.filter(x => x),
  arr2: arr.filter(x => x)
};

res.arr1.forEach(x => x.a = "a");

console.log(arr); //should print [{a: "one", b: "two"}]
console.log(res.arr1); //should print [{a: "a", b: "two"}]
console.log(res.arr2); //should print [{a: "one", b: "two"}]

If I change the values in arr1 array of object res then why the changes are applied to the arr2 and res also? filter creates new array then the effect should not be applied.

What I am doing wrong here?

Farhan Ghumra
  • 15,180
  • 6
  • 50
  • 115
  • Answer updated again : https://stackoverflow.com/a/49610148/1636522 :-D –  Apr 03 '18 at 21:22
  • Possible duplicate of [Why does changing an Array in JavaScript affect copies of the array?](https://stackoverflow.com/questions/6612385/why-does-changing-an-array-in-javascript-affect-copies-of-the-array) – Heretic Monkey Apr 03 '18 at 21:32

4 Answers4

5

Each element in the new array keeps the same object reference so you need to clone the object. In case there is no nested value then you can use Object.assign along with Array#map method. For deeper cloning, you need to use some other library or need to implement your own custom function.

var arr = [{a: "one", b: "two"}];
/* in real code I have actual filter condition, but the filtered result
share some common properties with value */
var res = {
  arr1: arr.map(x => Object.assign({}, x)),
  arr2: arr.map(x => Object.assign({}, x))
};

res.arr1.forEach(x => x.a = "a");

console.log(arr); //should print [{a: "one", b: "two"}]
console.log(res.arr1); //should print [{a: "a", b: "two"}]
console.log(res.arr2); //should print [{a: "one", b: "two"}]

FYI : What is the most efficient way to deep clone an object in JavaScript?

Pranav C Balan
  • 113,687
  • 23
  • 165
  • 188
1

The reason is that the array contains references to the objects, not copies of them. So while filter does returns a new array, the objects inside them still reference to the same objects.

So, when forEach is mutating the object referenced in res.arr1, it is modifying objects in all the arrays as they all point to the same reference.

Hope this helps.

Jatin Gupta
  • 889
  • 6
  • 15
0

Your items in the array are objects - they are reference types. Means that you have only one object and the rests are references to that same object. So changing it from one reference affects the result reading from another reference. You need to copy the items from the array. Here I used property spreading to copy the objects.

var arr = [{a: "one", b: "two"}];

var res = {
  arr1: arr.map(x => ({...x})),
  arr2: arr.map(x => ({...x}))
};

res.arr1.forEach(x => x.a = "a");

console.log(arr);
console.log(res.arr1);
console.log(res.arr2);
Suren Srapyan
  • 66,568
  • 14
  • 114
  • 112
0

When you write :

xs = [
  { p : true },
  { p : false }
];
ys = xs.filter(
  x => x.p
);

Here is how it looks like in memory :

xs         { p : false }
  \       /
   [ 0 , 1 ]
      \
       { p : true }
      /
   [ 0 ]
  /
ys

As you said, .filter() creates a new array, that's why xs and ys are linked to different arrays. Then, since xs[0].p is true, it makes a copy of xs[0] and pushes it to the new array. What you need to realize here is that xs[0] is a link to { p : true }, it's not the object itself, and ys[0] is a copy of this link. As a consequence, xs[0] and ys[0] are linked to the same object, and if you write xs[0].p = false, you can read the update with ys[0].p.

xs[0]
     \
      { p : false }
     /
ys[0]

If you want to avoid this situation, you have to copy the object itself :

function copy (x) {
  var y = {}; // new object
  for (var k in x) y[k] = x[k];
  return y;
}
ys[0] = copy(ys[0]);

Since copy() returns a new object, xs[0] and ys[0] are now linked to different objects, thus, changes to one object won't affect the other :

xs[0].p = true;
xs[0] --- { p : true }

ys[0] --- { p : false }

Regarding your code, arr[0] is a link to { a: "one", b: "two" }, and .filter() creates new arrays that contains a copy of this link pointing to the same object :

res.arr1[0]
           \
            \
arr[0] ----- { a: "one", b: "two" }
            /
           /
res.arr2[0]

Again, if you want to avoid this situation, you have to copy the object itself. However, since arr is likely to contain more than one object, you have to iterate over the array to copy every object one after the other. .map() is perfect for this job :

res.arr1 = arr.filter(f).map(copy);
res.arr2 = arr.filter(f).map(copy);
res.arr1[0] --- { a: "one", b: "two" }

arr[0] -------- { a: "one", b: "two" }

res.arr2[0] --- { a: "one", b: "two" }

Be careful though, it's a bit trickier when it comes to nested objects :

xs = [{ p: {} }]; // nested objects
ys = [copy(xs[0])];

In the above case, xs[0] and ys[0] are different, but xs[0].p and ys[0].p are the same :

xs[0]
     \
      { p }
         \
          {}
         /
      { p }
     /
ys[0]

In such a case, you have to make a deep copy of the object. This function will do the trick :

function copyTree (x) {
  if (x instanceof Array) {
    var copy = [];
    for (var i = 0; i < x.length; i++) {
      copy[i] = copyTree(x[i]);
    }
    return copy;
  } else if (x === Object(x) && typeof x !== "function") {
    var copy = {};
    for (var k in x) {
      copy[k] = copyTree(x[k]);
    }
    return copy;
  } else {
    return x;
  }
}
res.arr1 = arr.filter(f).map(copyTree);
res.arr2 = arr.filter(f).map(copyTree);

Note that the same problem arises with nested arrays, hence the array test above.