JavaScript clone A array with keys to B, than modify B's element

Viewed 63

I'm studying JavaScript about array assignment and cloning. I've tried to use spread operator "..." to clone the array A's elements to array B. However, why the Array A's element also changes when I modify B's elements ?

For example :

A = [{id:1, value:3},{id:2, value:1}];
B = [...A];
B[0].value = 4;
console.log(A[0].value);//the output changes to 4, not 3

I found that when cloning normal array, it won't happen this issue:

A = [3, 1];
B = [...A];
B[0] = 4;
console.log(A[0]);//the output is still 3

I know that in JavaScript, one of the purpose of cloning is to avoid two variables reference to the same memory, but why element in A would also be modified in the example?

If I misunderstand any concepts, hope you can help me to figure it out, or if there are any similar issues that were asked and solved before, it would be grateful that you can share the link.

6 Answers

Spread Operator or Object.assign() both does shallow cloning upto one level only. For deep cloning you may need to use a popular library called loadash and there you have a deepClone() function which you can use.

Also a very common trick that we use for JSON data, like you have (no functions etc) is we do the following:

let deepClone = JSON.parse(JSON.stringify(nodesArray));

So you may try this as well :)

The reason for that behavior is that in the first example you have an array of objects, which are reference variables as well.

You are creating new instance of an array itself, but you are not creating new objects. Instead of that, you passing reference to the same objects, which were used in the first array.

Spread creates a new reference for the array. But it only does one level of cloning. Now array A and array B point to different references.

But in case of objects (1st case), the items inside the array are still pointing to the same reference. This does not happen with primitive (2nd case). That is why doing B[0] affects the first element of array B which is a primitive and replaces it.

It might help if instead of thinking of A as holding the objects at index 0, 1, etc itself, but instead, think of it as holding references to those objects:

const A = [*refA*, *refB*];

Here refA and refB refer to your {id:1, value:3} and {id:2, value:1} objects respectively.

When you do [...A], each of these references is copied to the new array, but the references still refer to the same objects in memory:

B = [...A]; // `B` is [*refA*, *refB*]

So when you update B[0].value = 4, you are updating the object at the reference stored at B[0], ie: *refA*, and setting that object's value to 4. As both A and B refer to the same objects still (both hold *refA* in the first index), you're going to see changes in both A and B.

Rather, you can create a new array using .map(), and then map each object to a new object (so that you end up creating new references in your new array):

const B = A.map(obj => ({...obj}));

Above, we're creating a new object using {}, and then spreading the own (enumerable) properties from the object with ...obj into the new object {}. Because we've created a new object, the reference is unique, allowing you to update B[0].value = 4 without updating the objects refer to in A.

You need to deep clone Array. You are using ... operator which does shallow copy. So it is not succeeding.

Why the Array A's element also changes when I modify B's elements ?

Because objects typed values are copied by reference, whereas primitive typed values are copied by values. Hence, your second example doing a deep copy (values were copied) while first example is doing a shallow copy (reference were copied).

So that is what shallow copy and deep copy.

Now let's talk about the spread operator : It deep copies the data if it is not nested. For nested data, it deeply copies the first level data and shallow copies of the nested data.

So, If you want to deep copy your non-primitive data type values. You can go with structured clone algorithm.

Demo :

const A = [{id:1, value:3},{id:2, value:1}];
const B = structuredClone(A);
B[0].value = 4;
console.log(A[0].value); // 3
console.log(B[0].value); // 4

Related