Tutorial

How Deep Cloning Objects in JavaScript Works

JavaScript

Introduction

If you plan on coding with JavaScript, you need to understand how objects work. Objects are one of the most important elements of JavaScript, and a deep understanding of objects will always be useful.

It’s important to understand how to clone an object in JavaScript correctly. It is possible to create a shallow copy and a deep copy of an object. A shallow copy of an object references the original. So any changes made to the original object will be reflected in the copy. A deep copy is a copy of all elements of the original object. Changes made to the original object will not be reflected in the copy. In this article, you will create deep copies of objects using the Lodash library.

Prerequisites

To complete this tutorial, you will need the following:

Step 1 – Creating Shallow Copies by Reassigning Objects

If you create a function that takes in an object and alters it, you may want to create a copy of the object and change the copy instead of mutating the original object.

Initialize a new object and assign it to the variable testObject. This object should have letters a, b, and c as keys and 1, 2, and 3 as values, respectively.

Create the object in JavaScript:

let testObject = {
  a: 1,
  b: 2,
  c: 3
};

Now, try to create a copy of this object to manipulate by assigning the testObject to a new variable called testObjectCopy:

let testObject = {
  a: 1,
  b: 2,
  c: 3
};

let testObjectCopy = testObject;

Change the value for the key a in testObject. Set it equal to 9:

let testObject = {
  a: 1,
  b: 2,
  c: 3
};

let testObjectCopy = testObject;

testObject.a = 9;

You may expect this change to only be reflected in the testObject object. Use a console.log statement to check what the value for a is in testObjectCopy:

let testObject = {
  a: 1,
  b: 2,
  c: 3
};

let testObjectCopy = testObject;

testObject.a = 9;
console.log(testObjectCopy.a);

This console.log statement will print 9 to the console, despite referencing testObjectCopy and not testObject. This is because creating the new variable testObjectCopy doesn’t create a testObject copy. Instead, it’s referencing the testObject. Any changes you make to the original object will be reflected in the assumed copy and vice versa.

Reassigning objects to new variables only creates a shallow copy of the original object. In the next step, you will explore how looping through an object might be a possible solution to creating deep copies.

Step 2 – Creating Shallow Copies by Looping Through Objects

Looping through the object and copying each property to a new object may seem like a viable solution. To test this, create a function called copyObject that takes an argument called object:

const copyObject = object => {

};

Within copyObject, declare a variable called copiedObj which will hold an empty object:

const copyObject = object => {

  let copiedObj = {};
};

Create a for loop for each key in object. Set the key/value pairs in copiedObj equal to those in object:

const copyObject = object => {
  let copiedObj = {};

  for (let key in object) {
    copiedObj[key] = object[key];
  }
};

For the final step in this copyObject function, return copiedObj:

const copyObject = object => {
  let copiedObj = {};

  for (let key in object) {
    copiedObj[key] = object[key];
  }

  return copiedObj;
};

With copyObject in place, create an object called testObject and pass it in as a parameter for copyObject:

const copyObject = object => {

  let copiedObj = {};

  for (let key in object) {
    copiedObj[key] = object[key];
  }

  return copiedObj;
};

const testObject = {
  a: 5,
  b: 6,
  c: {
    d: 4
  }
};

copyObject(testObject);

To see the results of the copyObject function, use console.log to see the output of copyObject(testObject) printed to the console:

console.log(copyObject(testObject));

This will produce this output:

Output
{ a: 5, b: 6, c: { d: 4 } }

It may seem like looping through the testObject to create a copy produced the desired outcome. But there are several reasons why this approach won’t give you the results you’re looking for:

  • A loop that copies each property to a new object would only copy enumerable properties on the object. Enumerable properties are properties that will show up in for loops and Object.keys.
  • The copied object has a new Object.prototype method, which is not what you want when you copy an object. This means that whatever changes you make to the original object will be reflected in the copied object.
  • If your object has a property that is an object, your copied object will actually refer to the original instead of creating an actual copy. This means that if you change that nested object in the copied object, the original gets changed as well.
  • Any property descriptors are not copied. If you set things like configurable or writable to false, the property descriptors in the copied object will default to true.

Looping through objects allows you to create shallow copies, but it isn’t possible to create deep copies using this method. Thankfully, there is a library available that offers a solution to creating deep copies.

Step 3 – Creating Shallow and Deep Copies Using Lodash

For simple objects that only store primitive types like numbers and strings, shallow copying methods like the one above will work. However, if your object features references to other nested objects, the actual object won’t be copied. You would only be copying the reference.

For a deep copy, one great option is to use reliable external libraries like Lodash. Lodash is a library that offers two different functions that allow you to do shallow copies and deep copies. These are clone and clonedeep.

To test out the Lodash clone and clonedeep functions, you will need to install Lodash first:

  • npm install --save lodash

With lodash now installed, use the require() function to now access all the functions that Lodash has too offer:

const  _ = require('lodash');

Now you can use the clone and clonedeep functions in your code. Create an object called externalObject. Give a key animal with the value of 'Gator':

const externalObject = {
  animal: 'Gator'
};

Create another object called originalObject. originalObject will store seven properties with different values in each. The property d references externalObject which has the property of animal and a value of 'Gator'.

const originalObject = {
  a: 1,
  b: 'string',
  c: false,
  d: externalObject
};

Create Shallow Copies with clone

Declare a constant variable shallowClonedObject and assign it to a shallow copy of originalObject using the Lodash clone function:

const shallowClonedObject = _.clone(originalObject);

Reassign the value of the animal key in externalObject. Set it equal to 'Crocodile'. Use two console.log statements to print both originalObject and shallowClonedObject to the screen:

externalObject.animal = 'Crocodile';

console.log(originalObject);
console.log(shallowClonedObject);

The output of this code will look like this:

Output
{ a: 1, b: 'string', c: false, d: { animal: 'Crocodile' } } { a: 1, b: 'string', c: false, d: { animal: 'Crocodile' } }

Assigning the animal property in externalObject to a new value will change both originalObject and shallowClonedObject. The console.log statements will show this. This happens because the shallow clone was only able to copy the reference to the externalObject instead of creating a brand new object.

Create Deep Copies with clonedeep

You can create a deep copy by using the Lodash clonedeep function:

const deepClonedObject = _.clonedeep(originalObject);

With deepClonedObject in place, reassign the value of the animal key in externalObject to be equal to 'Lizard'.

Again, use two console.log statements to print both originalObject and deepClonedObject to the screen:

externalObject.animal = 'Lizard';

console.log(originalObject);
console.log(deepClonedObject);

The output of this code will look like this:

Output
{ a: 1, b: 'string', c: false, d: { animal: 'Lizard' } } { a: 1, b: 'string', c: false, d: { animal: 'Crocodile' } }

The 'animal' property in the originalObject changes, but for the
deepClonedObject, it remains as 'Crocodile' since the entire object was copied separately instead of copying the reference. Using the clonedeep function allows you to successfully create deep copies of objects.

Conclusion

It’s important to understand how to deep clone objects in JavaScript. You created shallow copies of objects by reassigning and looping through objects. You also used the Lodash library to create both shallow and deep copies of objects.

If you would like to learn more about objects in JavaScript, this Understanding Objects in JavaScript tutorial is a great place to start. If you’d like to take this a step further and learn how to copy object methods, the Copying Objects in JavaScript article can point you in the right direction.

Creative Commons License