There are 2 types by which we can copy an object - shallow copy and deep copy. And there is copy by reference which is actually not a copy in case of objects. Before starting that let’s see how an object is stored in the memory.
Objects (and arrays) are stored a little differently than strings, numbers, boolean, null or undefined, as Objects and arrays are non primitive data types.
Let’s copy a string to another variable.
let a = 'Drip Capital'
// This means we are copying the value of the variable a in variable b
let b = a
console.log(b)
// 'Drip Capital'
What would happen to the variable b if I change the value of variable a?
let a = 'Drip Capital'
// This means we are copying the value of the variable a in variable b
let b = a
a = 'Trade Finance'
console.log(b)
// Drip Capital
So that means variable a was just a pointer to the string value Drip Capital ?
let a = 'Drip Capital'
// Store (or point) the value 'Drip Capital' to the variable a
let b = a
// Now get the value of a and store (point) that to the variable b
a = 'Trade Finance'
// Remove the link for 'Drip Capital' and store (link) it to 'Trade Finance' instead
console.log(b)
// since we never changed b's value, thus
// Drip Capital
That’s how primitive data types work in Javascript.
Now let’s move on to Objects and arrays.
Objects don’t behave like primitive types, but what does that mean?
Let’s do the same thing that we did above but this time instead of a string value let’s take an object.
let a = {
company: 'Drip Capital'
}
let b = a
console.log(b.company)
// Drip Capital
Wait a minute! That’s exactly what was happening for the string value. So how exactly is it different?
If you are not planning to mutate or change the variable a or b or any property inside a or b, you will find no difference between these examples.
But things start to complicate when we play around with the variables a and b.
let a = {
company: 'Drip Capital'
}
let b = a
a.company = 'Google'
console.log(b.company)
// Google
Yes, changing the properties of a will change the properties of b as well. But why is that happening?
It’s because doing let a = b
will copy the reference of variable a to b, and there is one single object in the memory that is referred by both a and b, and so changing anyone of them will impact both variables.
Now what’s the solution?
...Spread operator to the rescue
let a = {
company: 'Drip Capital'
}
// This means, take all the properties of the variable a and put it in a new object
let b = {...a}
a.company = 'Google'
console.log(b.company)
// Drip Capital
So finally we have found the solution of how to copy an object. But wait! There’s more..
Consider a case with nested objects.
let a = {
company: 'Drip Capital',
address: {
city: 'Mumbai'
}
}
// Copy all properties to b
let b = {...a}
a.address.city = 'Palo Alto'
console.log(b.address.city)
// Palo Alto
Wait what?
If you look closely, you will see that a.company holds a string value, but address holds an object, so the concepts will stay the same whether the object is inside global space, function space, or even inside another object.
And since ...spread operator was only able to copy the properties of the object and failed to do so for nested objects, we got a SHALLOW COPY.
Does it mean you have to use the spread operator for all the nested objects?
Luckily there’s a better solution.
JSON.parse(JSON.stringify())
let a = {
company: 'Drip Capital',
address: {
city: 'Mumbai'
}
}
// DEEP Copy
let b = JSON.parse(JSON.stringify(a))
// very smart indeed
// Convert it into a string and then parse it back to an object.
a.address.city = 'Palo Alto'
console.log(b.address.city)
// Mumbai
And finally we got a proper copy as we wanted!
But there’s one more issue with this approach.
Consider another case
let a = {
company: 'Drip Capital',
address: {
city: 'Mumbai'
},
getSummary () {
return this.company + ' ' + this.address.city
}
}
// DEEP Copy
let b = JSON.parse(JSON.stringify(a))
console.log(b)
/*
address: {
city: "Mumbai"
},
company: "Drip Capital"
*/
So make sure that there is no methods defined in your object, before using JSON.parse & JSON.stringify
.
