In the previous article, we discussed the terms pass-by-value and pass-by-reference. If you haven't read that article yet, please go through this article (click here)"JavaScript Memory Management: Pass by Value VS Pass by Reference” , as we will be applying those concepts over here.
We saw that similar to any primitive data type (like integer
, string
, boolean
, etc.), we can't copy an object a
to object b
just by doing a = b
, since non-primitive types are passed by reference.
There might be 2 scenarios when we are dealing with objects
If the object doesn’t contain nested objects. (a simple scenario)
If the object contains nested objects .(objects,arrays,functions inside a object).
{…} The spread Operator
let a = { name: "Raghav", age: 20, city: "Bangalore" };
let b = { ...a }; // Spread operator
b.name = "Rahul"; // Modify 'b' to change its 'name'
console.log(a.name); // "Raghav" (a is unchanged)
console.log(b.name); // "Rahul" (b is updated)
Let’s look at the memory.
A has a memory address 0×100 in the stack. It points to a memory address 0x201 in the heap where the object is stored.
When
let b
is created, it gets a new memory address in the stack, say 0x101.The spread operator
{ ...a }
does not makeb
point to 0x201. Instead, it creates a new object in the heap at 0x202Now, the key-value pairs from
a
are copied one by one intob
. Sincea
does not have nested objects, all the values insidea
are primitive types like numbers, strings, and booleans.Primitive types are copied by value, so the spread operator makes a separate copy of the object.
Since
a
andb
are stored at different memory addresses, changingb.name
will not affecta.name
.
But what if there are nested objects??🤔
Shallow Copy vs Deep Copy
Before going further , understand this.
The concepts of shallow copy and deep copy typically arise only when dealing with nested structures (i.e., objects or arrays that contain other objects or arrays). Nowhere else.
let a = { name: "Raghav", details: { age: 20, city: "Bangalore" } };
let b = { ...a }; // Spread operator creates a shallow copy
a.details.city = "Chennai"; // Modifies the nested object in 'obj2'
console.log(a.details.city); // "Chennai" (because it's the same object)
console.log(b.details.city); // "Chennai"
Let us look at the memory now.
A has a memory address 0×100 in the stack. It points to a memory address 0x201 in the heap, where the object is stored.
When
let b
is created, it gets a new memory address in the stack, say 0x101.The spread operator
{ ...a }
does not makeb
point to 0x201. Instead, it creates a new object in the heap at 0x202.Since objects in JavaScript are stored in the heap, if
b
contains another object, a new memory address is created for that nested object as well, say 0×300.Now, the key-value pairs from
a
are copied one by one intob
:If the data type is primitive, it is copied by value.
If the data type is non-primitive, it is copied by reference.
Changing a primitive value in b
won’t affect a
, but changing a non-primitive value in b
will also change a
.
This is called shallow Copy. Even Object.assign({},a)
creates a shallow copy .
How do we create a complete copy of other element in such a way that even changing non primitive values wont make a difference??
Deep Copy
In JavaScript, you can create a deep copy of an object using multiple methods . Here will be discussing using structuredClone()
method.
Every step is similar to the shallow copy.
The only difference is that when there is a nested object, instead of copying it by reference, JavaScript creates a new memory address in the heap for that object and makes b
point to it.
let a = { name: "Raghav", details: { age: 20, city: "Bangalore" } };
let b = StructuredClone(a) // Spread operator creates a shallow copy
b.details.city = "Chennai"; // Modifies the nested object in 'obj2'
console.log(a.details.city); // "Banglore"
console.log(b.details.city); // "Chennai"
There are multiple ways to create a deep copy . We now know that the problem arises when objects are passed by reference. But since primitive data types are always passed by value, what if we could temporarily convert a non-primitive type into a primitive type, pass it by value, and then reconstruct it back into an object? 🤯
Sounds interesting? Stay tuned for the next article, where we explore Serialization & Deserialization—a powerful technique that can help us achieve this! 🚀”