Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Object copy using Spread operator actually shallow or deep?

I understand spread operators make Shallow copy of objects i.e the cloned object refers to the same reference as the original object. But the actual behaviour seems contradicting and confusing.

const oldObj = {a: {b: 10}};

const newObj = {...oldObj};

oldObj.a.b = 2;
newObj  //{a: {b: 2}}
oldObj  //{a: {b: 2}}

Above behaviour makes sense, newObj is also updated by updating oldObj because they refer the same location.

const oldWeirdObj = {a:5,b:3};

const newWeirdObj = {...oldWeirdObj};

oldWeirdObj.a=2;
oldWeirdObj      //{a:2,b:3}
newWeirdObj   //{a:5,b:3}

I'm not understanding, why newWeirdObj is not updating similar to oldWeirdObj? They are still referring to the same location if i am not wrong, but why is update to oldWeirdObj not updating newWeirdObj ?

like image 220
richa Singh Avatar asked Apr 25 '20 06:04

richa Singh


People also ask

Is spread operator deep or shallow copy?

For nested objects the spread operator will provide a deep copy to the first instance of the values but leaves all the nested data as shallow copies sharing a space in memory with original.

Is object spread a shallow copy?

1. Using Spread. Using spread will clone your object. Note this will be a shallow copy.

Can I use spread operator to copy object?

Use the object spread operator to clone an object or merge objects into one. Cloning is always shallow. When merging objects, the spread operator defines new properties while the Object.

How to deep copy an object in JavaScript?

Another way to deep copy objects in JavaScript is with the ES6 spread operator. Using the three dots ( ...) collects all values on the original object into another object. However, much like with Object.assign (), the spread operator only makes a partial copy. So any object with a nested object will not be deep copied.

What is a shallow copy in JavaScript?

Copying a value in JavaScript is almost always shallow, as opposed to deep. That means that changes to deeply nested values will be visible in the copy as well as the original. One way to create a shallow copy in JavaScript using the object spread operator ...:

What happens when you use spread operator to copy a nested object?

When you use them to copy a nested object, they will create a deep copy of the topmost data and a shallow copy of the nested data. Notice the previous example. keyboard.quantity was affected in both variables because the spread operator will copy values by reference when the variables are more than 1 dimension deep.

What does the spread operator do in JavaScript?

The spread operator makes deep copies of data if the data is not nested. When you have nested data in an array or object the spread operator will create a deep copy of the top most data and a shallow copy of the nested data. lets take a look at what this means. lets say I have an array c that looks like this:


2 Answers

So, for this problem, you have to understand what is the shallow copy and deep copy.

Shallow copy is a bit-wise copy of an object which makes a new object by copying the memory address of the original object. That is, it makes a new object by which memory addresses are the same as the original object.

Deep copy, copies all the fields with dynamically allocated memory. That is, every value of the copied object gets a new memory address rather than the original object.

Now, what a spread operator does? It deep copies the data if it is not nested. For nested data, it deeply copies the topmost data and shallow copies of the nested data.

In your example,

const oldObj = {a: {b: 10}};
const newObj = {...oldObj};

It deep copy the top level data, i.e. it gives the property a, a new memory address, but it shallow copy the nested object i.e. {b: 10} which is still now referring to the original oldObj's memory location.

If you don't believe me check the example,

const oldObj = {a: {b: 10}, c: 2};
const newObj = {...oldObj};

oldObj.a.b = 2; // It also changes the newObj `b` value as `newObj` and `oldObj`'s `b` property allocates the same memory address.
oldObj.c = 5; // It changes the oldObj `c` but untouched at the newObj



console.log('oldObj:', oldObj);
console.log('newObj:', newObj);
.as-console-wrapper {min-height: 100%!important; top: 0;}

You see the c property at the newObj is untouched.

How do I deep copy an object.

There are several ways I think. A common and popular way is to use JSON.stringify() and JSON.parse().

const oldObj = {a: {b: 10}, c: 2};
const newObj = JSON.parse(JSON.stringify(oldObj));

oldObj.a.b = 3;
oldObj.c = 4;

console.log('oldObj', oldObj);
console.log('newObj', newObj);
.as-console-wrapper {min-height: 100%!important; top: 0;}

Now, the newObj has completely new memory address and any changes on oldObj don't affect the newObj.

Another approach is to assign the oldObj properties one by one into newObj's newly assigned properties.

const oldObj = {a: {b: 10}, c: 2};
const newObj = {a: {b: oldObj.a.b}, c: oldObj.c};

oldObj.a.b = 3;
oldObj.c = 4;

console.log('oldObj:', oldObj);
console.log('newObj:', newObj);
.as-console-wrapper {min-height: 100%!important; top: 0;} 

There are some libraries available for deep-copy. You can use them too.

like image 161
Sajeeb Ahamed Avatar answered Oct 17 '22 22:10

Sajeeb Ahamed


Primitive typed values are copied by values, whereas objects typed values are copied by reference. In your second example both properties are primitive. that's why it's a deep copy (values were copied), but in the first example, the property is an object; hence it's a shallow copy (reference were copied).

Spread Operator

First you should understand how the spread operator really works. If the oldObj is printed as it's, then as you see the array is printed as the value assigned to it, but if you put oldObj inside of an object, then it's printed as '{oldObj: '}.

Now let's use the spread operator to oldObj while using it inside of an object, then as you see the key:value pair was taken out, and inserted into the outside object.

So this is what spread operator does. It's quite easy to understand it with arrays than objects. It removes elements from inside array, object, and insert them into the outside array, object. In array, if you use ...[1,2,3], it comes as 1,2,3, meaning elements were all removed from the array, and available as individual elements. With objects, you can't use as ...{key:'value'} but you can use as {...{key:'value'}} as then the key:value pair is inserted into the outer object ( {key:'value'} ) after removing from the inner object.

//Example 1
const oldObj = {a: {b: 10}};
console.log(oldObj); // { a: { b: 2 } }
console.log({oldObj}); // { oldObj: { a: { ... } } }
console.log({...oldObj}); // { a: { b: 2 } }

Your Example

In the second example, as I mentioned earlier, oldObj key:value pair was removed from the inner object, and included in the outer object; hence you get the same object.

{ oldObj : {a: {b: 10}} } -> {a: {b: 10}} // (with spread operator) 

The thing in here is a: has an object typed value; hence it's copied by REFERENCE, meaning when you copy oldObj to newObj, you copy the reference. That's why when you change oldObj.a.b, it affects to the newObj too.

//Example 2
const oldObj = {a: {b: 10}};
const newObj = {...oldObj};

oldObj.a.b = 2;
console.log(newObj)  //{a: {b: 2}}
console.log(oldObj)  //{a: {b: 2}}

In the third example, both the values of the a, b properties are primitive types. When primitive typed values are copied, you literally make a deep copy (copy values not reference). When you made a deep copy, and make a change to the old value, it doesn't affect to the new one, because both these properties are located in different locations of the memory.

//Example 3
const oldWeirdObj = {a:5,b:3};
const newWeirdObj = {...oldWeirdObj};

oldWeirdObj.a=2;
console.log(oldWeirdObj)     //{a:2,b:3}
console.log(newWeirdObj)   //{a:5,b:3}

If you want to copy by VALUES, then you have to copy each values instead of objects, as following. What happens in this example is, you assign {a: {b: 10}} to oldObj constant, then If you want to referer to property 'a' to access its value, you have to use as 'oldObj.a'. So you use the spread operator to it to take its value out, which is a primitive data typed value (10), then you use {a : X } as this to make a new object. Basically it goes like this a : {...{b: 10}} -> a : {b: 10}, meaning you get the same object with a deep copy. Now if you make a change to oldObj.a.b, it only affects to the old object, not the new one.

//Example 4
const oldObj = {a: {b: 10}};
const newObj = {a: {...oldObj.a}};

oldObj.a.b = 2;
console.log(newObj)  //{a: {b: 10}}
console.log(oldObj)  //{a: {b: 2}}
like image 3
Don Dilanga Avatar answered Oct 17 '22 22:10

Don Dilanga