JavaScript Object Trap Simplified by SilvenLEAF
Tags: javascript, webdev, codenewbie, silvenleaf
Published: 2021-11-28 13:52:53

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**
```javascript
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.
1. **Primitive Data Types**: e.g. Number, String, Boolean, null, undefined


2. **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.

```javascript
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

```javascript
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. 

```javascript
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:
```javascript
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:
```javascript
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
```javascript
// 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:
```javascript
// "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
```javascript
// 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
```
***
#### More on Object Cloning
##### Deep Clone vs Shallow Clone
Since we are talking about cloning, you might encounter these two words at some point. What is a deep clone and a shallow clone? 

When we clone a variable from another variable, if both of them are totally independant and not linked in anyway (including all their nested values), I mean if I change one then it does not change the other, this cloning is called **Deep cloning**.

You can change all nested values of y and it should not change the values of x
```javascript
let x = { 
  name: 'SilvenLEAF', age: 19,
  more_info: {
    favorite_language: 'TypeScript',
    total_languages: 7,
  } 
}
let y = JSON.parse(JSON.stringify(x)); // I cloned x to y

console.log(y)
console.log(x)
/*
Both of them have these values now
{ 
  name: 'SilvenLEAF', age: 19,
  more_info: {
    favorite_language: 'TypeScript',
    total_languages: 7,
  } 
}
*/


y.name = 'Manash'
y.more_info.favorite_language = 'Chinese'

console.log(y)
/*
{ 
  name: 'Manash', age: 19,
  more_info: {
    favorite_language: 'Chinese',
    total_languages: 7,
  } 
}
*/
console.log(x) 
/*
{ 
  name: 'SilvenLEAF', age: 19,
  more_info: {
    favorite_language: 'TypeScript',
    total_languages: 7,
  } 
}
*/


// You can change any value of y and it will not change x
```

But, if any nested object in that clone maintains their reference, I mean if you change that nested object and it also changes the nested object from the original parent, then this cloning is called **Shallow cloning**,

```javascript
let x = { 
  name: 'SilvenLEAF', age: 19,
  more_info: {
    favorite_language: 'TypeScript',
    total_languages: 7,
  } 
}

// method 1
let y = {...x} 

// method 2
let y = Object.assign({}, x) 

y.name = 'Manash';
y.more_info.favorite_language = 'Chinese';

console.log(y) 
/*
{ 
  name: 'Manash', age: 19,
  more_info: {
    favorite_language: 'Chinese',
    total_languages: 7,
  } 
}
*/
console.log(x) 
/*
{ 
  name: 'SilvenLEAF', age: 19,
  more_info: {
    favorite_language: 'Chinese',
    total_languages: 7,
  } 
}

// When I changed y.name it did not change x.name 
// but when I changed y.more_info, it also changed x.more_info as well
// because x.more_info is a referencial value and when we cloned x into y with the above methods
// it cloned and created a new object but y.more_info is still maintaining the reference of x.more_info
// these two are still pointing to the same object
```

### Conquering Object Trap
Well we saw that we can't deep clone the object with these following methods
```javascript
let x =  { 
  name: 'SilvenLEAF', age: 19,
  more_info: {
    favorite_language: 'TypeScript',
    total_languages: 7,
  } 
}

// Method 1 (Assigning reference for nested objects)
let y = x;
/*
  This is NOT a shallow cloning 
  but assigning the existing object to y by reference. 
  Thanks goes to @jamesthomson for pointing that out
*/

// 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 Method 2
```
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!!
```javascript
let x = { 
  name: 'SilvenLEAF', age: 19,
  more_info: {
    favorite_language: 'TypeScript',
    total_languages: 7,
  } 
}
let y = JSON.parse(JSON.stringify(x)); // we cloned x to y
```
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 anything from y, it will not change anything from x
```javascript
let x = { 
  name: 'SilvenLEAF', age: 19,
  more_info: {
    favorite_language: 'TypeScript',
    total_languages: 7,
  } 
}
let y = JSON.parse(JSON.stringify(x)); // we cloned x to y

console.log(y)
console.log(x)
/*
Both of them have these values now
{ 
  name: 'SilvenLEAF', age: 19,
  more_info: {
    favorite_language: 'TypeScript',
    total_languages: 7,
  } 
}
*/


y.name = 'Manash'
y.more_info.favorite_language = 'Chinese'

console.log(y)
/*
{ 
  name: 'Manash', age: 19,
  more_info: {
    favorite_language: 'Chinese',
    total_languages: 7,
  } 
}
*/
console.log(x) 
/*
{ 
  name: 'SilvenLEAF', age: 19,
  more_info: {
    favorite_language: 'TypeScript',
    total_languages: 7,
  } 
}
*/


// You can change any value of y and it will not change x
```
Yea, I know it's a bit clumsy method. But that's what I could find. Please share in the comments if you know any better solution.

##### Thanks to @JamesThomson
"JSON.stringify is one method and is acceptable in some use cases, however it's not a foolproof method as it will destroy nested functions within the object." --- James Thomson

"To truely perform a deep clone, you have to loop through the objects contents, see Lodash for an example on this." --- James Thomson
***
***
### Advanced Section
##### Why does "let y = {...x}" not work?
```javascript
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
```javascript
// "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,
```javascript
// "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.


# 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](https://www.linkedin.com/in/silvenleaf/)

Wanna know more about me? Come here! 
[SilvenLEAF.github.io](https://SilvenLEAF.github.io)