Cloning an object in JavaScript and avoiding Gotchas
Tags: javascript
Published: 2020-09-29 21:13:10

If you're a JavaScript developer, you must have come across scenarios where you need to clone an object. How do you do it? In this article we will cover various approaches to clone an object in JavaScript and their shortcomings and finally talk about the most reliable way to make a deep copy (clone) of an object in JavaScript.

Let us consider that our object to be cloned is this:

```
const person = {
  name: 'Dolores Abernathy',
  age: 32,
  dob: new Date('1988-09-01')
}
```

There can be various ways to clone it:

One way would be to declare a new variable and point it to the original object (which is not exactly cloning the object)

```
const clone = person
```

What you're doing here is you are referencing the same object. If you change `clone.name`, `person.name` will also change. Most of the times, this is not what you intend to do when you want to clone an object. You would want a copy of the object which does not share anything with the original object. Here, `clone` is just a reference to the same object being referred by `person`. Most of the JavaScript developers would know about this. So, this is not really a "Gotcha!". But the next two approaches I am going to show are definitely something you need to watch out for.


You'll often see code using spread operator to clone an object. For example:

```
const clone = { ...person }
```

Or code using `Object.assign` like this

```
const clone = Object.assign({}, person)
```

One might assume in both of the above cases that `clone` is a copy of the original `person` object and does not share anything with the original object. This is partially correct but can you guess the output of the code below? (Please take a moment to think what the output should be before copy pasting it)

```
const person = {
  name: 'Dolores Abernathy',
  age: 32,
  dob: new Date('1988-09-01')
}

const clone = { ...person }

// change the year for person.dob
person.dob.setYear(1986)

// check the clone's dob year
console.log(clone.dob.getFullYear())
```

What was your guess? `1988`? 

![2020-09-27-02-49-13](https://codingfundas.com/content/images/2020/09/2020-09-27-02-49-13.png)


The correct answer is `1986`. If you guessed the right answer and know the reason behind it, good! You have strong JavaScript fundamentals. But if you guessed it wrong, that's ok. It's the reason why I am sharing this blog post because a lot of us assume that by using the spread operator, we are creating a completely separate copy of the object. But this is not true. Same thing would happen with `Object.assign({}, person)` as well. 

Both these approaches create a shallow copy of the original object. What does that mean? It means that all the fields of the original object which are primitive data types, will be copied by value but the object data types will be copied by reference.

In our original object, `name` and `age` are both primitive data types. So, changing `person.name` or `person.age` does not affect those fields in the `clone` object. However, `dob` is a `date` field which is not a primitive datatype. Hence, it is passed by reference. And when we change anything in `dob` field of the `person` object, we also modify the same in `clone` object.

### How to create a deep copy of an object ?

Now that we know that both the spread operator and the `Object.assign` method create shallow copies of an object, how do we create a deep copy. When I say deep copy, I mean that the cloned object should be a completely independent copy of the original object and changing anything in one of those should not change anything in the other one.

Some people try `JSON.parse` and `JSON.stringify` combination for this. For example:


```
const person = {
  name: 'Dolores Abernathy',
  age: 32,
  dob: new Date('1988-09-01')
}

const clone = JSON.parse(JSON.stringify(person))
```

While it's not a bad approach, it has its shortcomings and you need to understand where to avoid using this approach.


In our example, `dob` is a date field. When we do `JSON.stringify`, it is converted to date string. And then when we do `JSON.parse`, the `dob` field remains a string and is not converted back to a date object. So, while `clone` is a completely independent copy of the `person` in this case, it is not an exact copy because the data type of `dob` field is different in both the objects.


You can try yourself

```
console.log(person.dob.constructor) // [Function: Date]
console.log(clone.dob.constructor) // [Function: String]
```

This approach also doesn't work if any of the fields in the original object is a function. For example


```
const person = {
  name: 'Dolores Abernathy',
  age: 32,
  dob: new Date('1988-09-01'),
  getFirstName: function() {
    console.log(this.name.split(' ')[0])
  }
}

const clone = JSON.parse(JSON.stringify(person))

console.log(Object.keys(person)) // [ 'name', 'age', 'dob', 'getFirstName' ]

console.log(Object.keys(clone)) // [ 'name', 'age', 'dob' ]

```

Notice that the `getFirstName` is missing in the clone object because it was skipped in the `JSON.stringify` operation as it is a function.

### What is a reliable way to make a deep copy/clone of an object then?

Up until now all the approaches we have discussed have had some shortcomings. Now we will talk about the aproach that doesn't. If you need to make a truly deep clone of an object in JavaScript, use a third party library like `lodash`


```
const _ = require('lodash')

const person = {
  name: 'Dolores Abernathy',
  age: 32,
  dob: new Date('1988-09-01'),
  getFirstName: function() {
    console.log(this.name.split(' ')[0])
  }
}

const clone = _.cloneDeep(person)

// change the year for person.dob
person.dob.setYear(1986)

// check clone's dob year
console.log(clone.dob.getFullYear() // should be 1988

// Check that all fields (including function getFirstName) are copied to new object
console.log(Object.keys(clone)) // [ 'name', 'age', 'dob', 'getFirstName' ]

// check the data type of dob field in clone
console.log(clone.dob.constructor) // [Function: Date]

```

You can see that the `cloneDeep` function of `lodash` library will make a truly deep copy of an object.


### Conclusion

Now that you know different ways of copying an object in JavaScript and pros and cons of each approach, I hope that this will help you in making a more informed decision on which approach to use for your use case and avoid any "Gotchas" while writing code.


Happy Coding :-)