Be aware when cloning objects in JavaScript! 👯‍♀️

Featured on Hashnode

Now and then you'll need to clone an object in JavaScript, mainly one to be modified but you still want the original to stay the same.

Let's say for this purpose we have a duplicate and change function.

Meaning we have an object, which we will duplicate and then change. The original, of course, will need to stay the same.

Benchmark JavaScript clone

In JavaScript we can ofcourse clone a object by assigning it to a new const like this:

const original = { color: '🔴', child: { action: 'stop' } };
const clone = original;

console.log(original); // {color: "🔴", child: {action: "stop"}}
console.log(clone); // {color: "🔴", child: {action: "stop"}}

Ok wow that works, cool done!

But not really, here comes the issue with this.

clone.color = '🟢';
console.log(original); // {color: "🟢", child: {action: "stop"}}
console.log(clone); // {color: "🟢", child: {action: "stop"}}

Hmm that's not cool, now our original one is also modified!

This is caused because of objects being reference types. When we choose to use = to clone, we make a pointer to object one instead of actually cloning it.

Clone using the spread operator

The spread operator is introduced in ES6 so fairly new, and not in the official specs yet!

To use it, you create a new object prefixed by three dots ...

const original = { color: '🔴', child: { action: 'stop' } };
const spread = {...original};
console.log(original); // { color: '🔴', child: { action: 'stop' } }
console.log(spread); // { color: '🔴', child: { action: 'stop' } }

Ok, our basic clone works again, now let's check the change

spread.color = '🟢';
console.log(original); // { color: '🔴', child: { action: 'stop' } }
console.log(spread); // { color: '🟢', child: { action: 'stop' } }

Yes, we did it!

But wait let's make sure we can also modify the child element.

spread.color = '🟢';
spread.child.action = 'start';
console.log(original); // { color: '🔴', child: { action: 'start' } }
console.log(spread); // { color: '🟢', child: { action: 'start' } }

Wait, what just happened?

This is because the spread operator is a shallow copy, so only the first level will be actually copied, the rest will be assigned.

Clone in JavaScript using Object.assign

This is by far the method you will read the most about. It's basically the old version of the spread operator.

You can use it as follows:

const original = { color: '🔴', child: { action: 'stop' } };
const assign = Object.assign({}, original);
console.log(original); // { color: '🔴', child: { action: 'stop' } }
console.log(assign); // { color: '🔴', child: { action: 'stop' } }

Cool, this also clones, but let's check if it modifies correctly.

assign.color = '🟢';
assign.child.action = 'start';
console.log(original); // { color: '🔴', child: { action: 'start' } }
console.log(assign); // { color: '🟢', child: { action: 'start' } }

Damn, still the same issue turns out Object.assign is also a shallow copy.

So now what?

Using JSON to clone

A quick and dirty hack to deep-clone is using JSON to stringify and then parse the object again.

This is not a "best-practice" but used by many people, and good enough for basic cloning.

For more robust deep-clone make use of packages like lodash cloneDeep.

It works like this:

const original = { color: '🔴', child: { action: 'stop' } };
const json = JSON.parse(JSON.stringify(original));
console.log(original); // { color: '🔴', child: { action: 'stop' } }
console.log(json); // { color: '🔴', child: { action: 'stop' } }

Awesome, does the exact same thing, but let's see when we modify data.

json.color = '🟢';
json.child.action = 'start';
console.log(original); // { color: '🔴', child: { action: 'stop' } }
console.log(json); // { color: '🟢', child: { action: 'start' } }

Yes, we did it! A fully cloned object that we can modify!

Thank you for reading, and let's connect!

Thank you for reading my blog. Feel free to subscribe to my email newsletter and connect on Facebook or Twitter

Jarrod Connolly's photo

A great introduction to shallow vs deep cloning in JavaScript.

Be careful using JSON parse/stringify to deep clone as it does not support some JavaScript features when used to clone objects. Date objects will convert to strings. Functions, symbols, and undefined are also not correctly converted over.

Just a couple of things to watch out for if one is looking for a proper deep clone. Better off to use something like lodash cloneDeep for correctness and performance reasons.

Chris Bongers's photo

Hey Jarrod,

Yes as mentioned in the article for robust deep-cloning one should use lodash deepclone or any other library.

For simple "flat" objects this JSON method works really well.

It does not work for Data objects for instance, they will forget that they are actual date objects.

Francesco Strazzante's photo

Great and useful post! Thank you Chris. 😎 Years ago I remember I used cloneDeep: the function from lodash, but it tooks a while to understand the problem. This pointer creation when use "=" it's not widely known unfortunately.

Chris Bongers's photo

Hey Francesco, Haven't used the deepClone often myself, what exactly happens when you use the =? I mainly choose the JSON method or make my own deepClone specific for typecasting.

Shamaayil Ahmed's photo

Gonna take care.

Chris Bongers's photo

Awesome Shamaayil, Thanks for taking the time to comment on my article, appreciate it 🤟