This content originally appeared on DEV Community and was authored by SilvenLEAF
Ahoy there! Let's meet the biggest Object Trap in JavaScript!!
Did you know that you can not copy an object? I mean you can't use any of these following methods to purely copy an object in JavaScript.
If you use these methods, you'll get really unexpected results. Like, if you change y, it'll also change x. (Same for arrays too)
These methods will NOT work
let x = { name: 'SilvenLEAF', age: 19 }
// Method 1
let y = x
// Method 2
let y = {...x}
// Method 3
let y = Object.assign(x)
Did you know why it happens? And also how to solve it?
If not, let's dive in depth why and how JavaScript does that.
Data Types in JavaScript
There are 2 types of data in JavaScript.
Primitive Data Types: e.g. Number, String, Boolean, null, undefined
Referencial Data Types: e.g. Objects, Arrays
Main Concept
When we store a primitive value in a variable, we are storing the value in that variable. But, when we are storing a referencial value in a variable, we are storing its reference in that variable.
let x = SOME_VALUE;
let y = x;
If x is a primitive data type then, y will be a pure copy of x where x and y are not linked in any way. I mean, if you change the value of y, it will not affect the value of x
let x = 5
let y = x
console.log(y) // 5;
y = 7
console.log(y) // 7
console.log(x) // 5
But if x is a referencial value, then y and x will be two variables for the same value. So if you change y, it'll also change x. Because they are just two names for the same object.
let x = { name: 'SilvenLEAF', age: 19, isFemale: false } // here x is the reference for this object
let y = x // now both x and y are referencing that same object, so you can say, two names for the same object
console.log(y) // { name: 'SilvenLEAF', age: 19, isFemale: false }
y.name = 'Manash'
console.log(y) // { name: 'Manash', age: 19, isFemale: false }
console.log(x) //{ name: 'Manash', age: 19, isFemale: false }
Same thing is applicable for Arrays as well:
let x = [1,2,3,4,5]
let y = x
console.log(y) // [1,2,3,4,5]
y[0] = 'a'
console.log(y) // ['a',2,3,4,5]
console.log(x) // ['a',2,3,4,5]
Analogy:
Now let's understand this concept with simplest analogies.
Analogy for Primitive Data Type:
let x = 'water'
// In the x bucket we have 5 litre water.
let y = x
// Hey computer, in the y bucket, store the same kind of thing that we have on x
// The computer stores 5 litre water in the y bucket as well
y = 'apples'
// Hey computer, I changed my mind,
// Keep 5 apples in the y bucket
console.log(y)
// What do I have in my y bucket now?
// 5 apples
console.log(x)
// What do I have in my x bucket?
// 5 litre water
// Why?
// We changed the content of y bucket
// but we did not change the content of x bucket
// NOTE: x bucket and y backet had seperate 5 litre water.
// Not the same water in both buckets.
// Because I told the computer to store the same type and same amount of thing that we had on x bucket
Another analogy if you are still confused
// Another analogy is:
let x = 'chocolate'
// x girl buys a chocolate
y = x
// y girl tells her dad, "Daddy daddy, what is she (x girl) buying? I wanna have the same"
// Now her dad gives her that type of chocolate.
// Now both x and y have same type of chocolate
y = 'ice cream'
// y girl changes her mind, "Yuck, I don't like this flavour,
// I don't want it anymore, give me ice cream instead"
// Her dad now buys her an 'ice cream'
// Now y girl has an ice cream. What does x girl have?
// A chocolate. Because y girl changed her mind.
// It doesn't change the fact that x girl bought a chocolate
// Hope you get my point
Analogy for Referencial Data Type:
// "The_book_on_the_3rd_drawer" is this book
let The_book_on_the_3rd_drawer = {
title: 'A book of insanity',
author: 'SilvenLEAF',
rating: 9,
}
let the_weird_book = The_book_on_the_3rd_drawer
// the_weird_book is now referencing The_book_on_the_3rd_drawer
// Hey computer, name the book on my 3rd drawer as the_weird_book.
// So in future if I say, "Change the title of the_weird_book",
// you will change the title of that book (which is the book on my 3rd drawer).
let my_favorite_book = the_weird_book
// Hey, name the_weird_book as my_favorite_book.
// Hey, name the book on my 3rd drawer as my_favorite_book
// So now the book on my third drawer has two names, the_weird_book and my_favorite_book
// So if I say, where is the_weird_book?
// It is in your 3rd drawer my master
// Then where is my_favorite_book?
// It is in your 3rd drawer my master
// Why?
// Because they are the same book with 2 names
my_favorite_book.author = 'Manash'
// make the author of my_favorite_book as 'Manash'
console.log(my_favorite_book) // { title: 'A book of insanity', author: 'Manash', rating: 9 }
console.log(the_weird_book) // { title: 'A book of insanity', author: 'Manash', rating: 9 }
// Who is the author of my_favorite_book?
// Manash
// Who is the author of the_weird_book?
// Manash
// Why?
// Because you gave two names for the same book.
Another analogy if you are still confused
// here SilvenLEAF is this boy
let SilvenLEAF = {
name: 'Manash Sarma',
age: 19,
what_I_like_about_him: 'His projects'
}
let the_clumpsy_kid = SilvenLEAF
// Hey computer, let's call SilvenLEAF as the_clumpsy_kid
let the_typescript_addict = the_clumpsy_kid
// Hey computer, let's call that clumpsy kid as "the typescript addict"
// Hey computer, let's call SilvenLEAF as "the typescript addict"
the_typescript_addict.what_I_like_about_him = 'His blogs'
// Hey computer, update this info, what I like about the typescript addict is his projects
console.log(the_typescript_addict)
console.log(the_clumpsy_kid)
// Both has this value {
// name: 'Manash Sarma',
// age: 19,
// what_I_like_about_him: 'His blogs'
// }
// Hey what is the thing I like about the clumpsy kid?
// His blogs
// Hey what is the thing I like about the typescript addict?
// His blogs
// Why?
// They are the same boy.
// You gave two names for the same boy
Deep Clone vs Shallow Clone
So what is a deep clone and a shallow clone?
When we clone a variable from another variable, if both of them are independant and not linked in anyway, I mean if I change one then it does not change the other, this cloning is called Deep cloning.
let x = 5;
let y = x; // I cloned x to y
console.log(y) //5
console.log(x) //5
y = 7
console.log(y) //7
console.log(x) //5
// when I changed y it did not change x
But, if I changed one and it changed the other, this cloning will be called Shallow cloning because they are cloned from the outside but are same from the inside. They both are the same object.
let x = { name: 'SilvenLEAF', age: 19 }
let y = x // both x and y are same object
y.name = 'Manash'
console.log(y) // { name: 'Manash', age: 19 }
console.log(x) // { name: 'Manash', age: 19 }
// When I changed y, it changed x as well
Conquering Object Trap
Well we saw that we can't deep clone the object with these following methods
let x = { name: 'SilvenLEAF', age: 19 }
// Method 1 (Shallow cloning)
let y = x;
// Method 2 (Shallow cloning)
let y = {...x};
/*
it will work if x has only primitive values,
but if x has a referencial value inside it
(I mean, an object or an array inside x)
then it will not work for that referencial value.
We'll discuss about it in the advanced section below
*/
// Method 3 (Shallow cloning)
let y = Object.assign(x);
// This one is same as y = x
Then how do we make a deep clone? (Deep clone means having same value but totally independant and not linked in any way, so that if we change one, the other one will not get changed)
It's super EASY!!
let x = { name: 'SilvenLEAF', age: 19 }
let y = JSON.parse(JSON.stringify(x))
Why does it work? "JSON.stringify()" turns x into a primitive value, and as we know, if it is a primitive value, it'll make a pure deep clone. Now we are converting the pure deep clone (the JSON string) into an object. And this object is purely independant and not linked to x in any way
So now if you change y, it will not change x
y.name = 'Manash'
console.log(y) // { name: 'Manash', age: 19 }
console.log(x) // { name: 'SilvenLEAF', age: 19 }
Yea, I know it's a bit clumsy method. But that's what I could find. Please share in the comments if you know another solution.
Advanced Section
Why does "let y = {...x}" not work?
let objectA = {
name: 'SilvenLEAF',
age: 19,
blogs: {
source: 'Dev.to',
themeColor: 'red',
}
}
// here objectA has "name" and "age" two primitive values and "blogs" referencial value
let objectB = { ...objectA };
console.log(objectB)
console.log(objectA) // both of their content is same as objectA
objectB.name = 'Manash'
objectB.blogs.source = 'Hashnode'
console.log(objectB)
/*
{
name: 'Manash',
age: 19,
blogs: {
source: 'Hashnode',
themeColor: 'red',
}
}
*/
console.log(objectA)
/*
{
name: 'SilvenLEAF',
age: 19,
blogs: {
source: 'Hashnode',
themeColor: 'red',
}
}
*/
Look, the primitive values of objectB are independant and not linked to those of objectA. But, for the referencial value of objectB is still linked to the referencial value of objectA.
So when we changed "objectB.name" it did not change "objectA.name". But when we changed "objectB.blogs" it also changed "objectA.blogs" because they both are the reference of the same object.
Now still confused. Don't worry, let's see what the spread operator actually is
// "let y = {...x}" actually means this
let y = {
name: x.name, // deep clone (because name is primitive)
age: x.age, // deep clone (because name is primitive)
blogs: x.blogs, // shallow clone (because name is referencial)
}
Or in other words,
// "let y = {...x}" actually means this
let y = {};
y.name = x.name // deep clone (because name is primitive)
y.age = x.age // deep clone (because name is primitive)
y.blogs = x.blogs // shallow clone (because name is referencial)
Now that makes sense right?
Why does "let y = Object.assign(x)" not work?
Same as "let y = {...x}" explained above
Congratulation if you made it this far. Hopefully I was able to clarify it. Let me know if you are still confused.
NEXT blog is coming by December 5th
What's NEXT?
1. Learning DevOps with Github Actions
2. More on DevOps
3. Improved AI BOT that can do anything
4. Insane stuff with JavaScript/TypeScript
5. Debugging TypeScript with VS Code Debugger
6. Sequelize Hooks
7. How to create an Android APP with NO XP
(including apk generating)
Got any doubt?
Drop a comment or Feel free to reach out to me @SilveLEAF on Twitter or Linkedin
Wanna know more about me? Come here!
SilvenLEAF.github.io
This content originally appeared on DEV Community and was authored by SilvenLEAF
SilvenLEAF | Sciencx (2021-11-28T13:52:53+00:00) JavaScript Object Trap Simplified by SilvenLEAF. Retrieved from https://www.scien.cx/2021/11/28/javascript-object-trap-simplified-by-silvenleaf/
Please log in to upload a file.
There are no updates yet.
Click the Upload button above to add an update.