Since JavaScript objects are reference types, you can not just use the equal operator (=) to copy an object. When you create an object in JavaScript, the value is not directory assigned to the variable. Instead, the variable only holds a reference to the value.

What's Reference Type?

Let us look at the following example to understand what reference type means:

const obj1 = { mango: '🥭️', apple: '🍎' };

const obj2 = obj1;

console.log(
    obj1, // { mango: '🥭️', apple: '🍎' }
    obj2  // { mango: '🥭️', apple: '🍎' }
);

As you can see above, I have created an object and then assigned it to a new variable by using the = operator. Both objects output the same key-value pairs. So far, so good!

Let us now add a new key to the first object to see what happens:

obj1.lemon = '🍋';

console.log(
    obj1, // { mango: '🥭️', apple: '🍎', lemon: '🍋' } ✅
    obj2  // { mango: '🥭️', apple: '🍎', lemon: '🍋' } ❌
);

You can see I only made changes to obj1 but it has affected obj2 as well. This is not what we expect when we copy an object. It happens because objects are reference types and when we use =, it only copies the pointer to the memory allocated to the object and not the actual value.

Shallow Clone vs. Deep Clone

A shallow clone only copies primitive types like strings, numbers, and booleans available in the object. Any nested object or array will not be recursively copied. Instead, only a reference to the object is copied to the new object. It means that both the original object and copied object continue to refer the same nested object.

If the original object references other external objects, they are also not recursively copied when creating a shallow copy of the object. Only the references to the external objects are copied.

On the other hand, a deep clone recursively copies everything: primitive data types, nested and external objects, arrays, functions, dates, and so on. The cloned object is completely independent of the original object.

JavaScript offers many ways to create shallow and deep clones of objects. You can use the spread operator (...) and Object.assign() method to quickly create a shallow object duplicate. For the deep cloning of objects, you can either write your own custom function or use a 3rd-party library like Lodash.

Object.assign() Method

The simplest and faster way to create a shallow copy of an object is by using ES6's Object.assign(target, source1, soure2, ...) method. This method copies all enumerable own properties of one or more source objects to a target object, and returns the target object:

const fruits = { mango: '🥭️', apple: '🍎' };

const moreFruits = Object.assign({}, fruits);

console.log(moreFruits);
// { mango: '🥭️', apple: '🍎' }

Notice the empty {} source object as the first parameter. This is necessary to make sure that the original object is not altered. This method lacks support for old browsers like IE, and only works in modern browsers.

Spread Operator

The spread operator (...) is yet another ES6 feature that provides a simple way to perform a shallow clone of an object, equivalent to what Object.assign() does:

const fruits = { mango: '🥭️', apple: '🍎' };

const moreFruits = { ...fruits };

console.log(moreFruits);
// { mango: '🥭️', apple: '🍎' }

Although spread operators are around since ES6 (ESMAScript 2015), the support for cloning objects was only introduced recently in ES9 (ESMAScript 2018). So you should only consider using this approach for the latest versions of modern browsers.

JSON Methods

If your object only contains primitive types, and doesn't include nested or external objects, arrays, Date objects, functions, and so on, you can easily create a deep clone of the object by using JSON methods: JSON.stringify() and JSON.parse():

const fruits = { mango: '🥭️', apple: '🍎' };

const moreFruits = JSON.parse(JSON.stringify(fruits));

console.log(moreFruits);
// { mango: '🥭️', apple: '🍎' }

This approach works great in all modern browsers and IE8+. However, there are two downsides:

  • The object must be compatible with the JSON format. This means that the nested objects must be JSON serializable and deserializable.
  • It is slower than other solutions when the object contains a lot of properties.

The JSON methods only support strings, numbers, and object literals without functions and symbol properties. You'd see a weird behavior when the object contains non-compatible values:

// undefined is omitted
// Infinity is turned to null
JSON.parse(JSON.stringify({ a: undefined, b: Infinity })); 

// { b: null }

// Date object is turned to string
JSON.parse(JSON.stringify({ a: new Date() })); 

// { a: "2020-06-16T19:44:57.492Z" }

// function is omitted too
JSON.parse(JSON.stringify({ a: () => { return 'Hi'; } })); 

// {}

You should only use this approach for JSON compatible objects. For objects that contain JSON incompatible values, consider using a 3rd-party library like Lodash to create a deep clone.

Lodash's cloneDeep() Method

Lodash provides the cloneDeep() method that recursively copies everything in the original object to the new object. It works for all data types, including functions, nested objects, arrays, and symbols.

Here is an example:

const _ = require('lodash');

const obj = {
    name: 'John Doe',
    age: 45,
    address: {
        city: 'Berlin',
        country: 'DE'
    },
    job: undefined,
    credits: Infinity
};

const cloned = _.cloneDeep(obj);

console.log(cloned);

// {
//     name: 'John Doe',
//     age: 45,
//     address: { city: 'Berlin', country: 'DE' },
//     job: undefined
//     credits: Infinity
// }

To learn more about JavaScript objects, prototypes, and classes, take a look at this article.

Read Next: How to copy an array in JavaScript

✌️ Like this article? Follow @attacomsian on Twitter. You can also follow me on LinkedIn and DEV. Subscribe to RSS Feed.

👋 If you enjoy reading my articles and want to support me to continue creating free tutorials, ☕ Buy me a coffee (cost $5).