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
Top comments (17)
If you want to do it yourself for simple cases, you can just use recursion:
But beware of cyclic references, i.e.
To avoid that, you'll need to store references, for which a Map is easily the best possible option, but you need to make sure you fill the map with a reference/clone pair before you start the recursion:
Now cyclic references are no longer an issue:
That still doesn't handle stuff like constructed objects, though (Date, Headers, etc.).
Hey, that basically will do the same as the JSON method right?
This is one way to sort of deep-clone, haven't tested this fully:
Response:
Yes and no. JSON.parse/stringify will serialize and deserialize the data. Some types are not supported and cyclic references will also lead to errors. But putting both variants together, you'll get:
Now that works for all usual types. It will fail for some of the modern things like Map/WeakMap etc.
It all goes deeper than you would think, wonder why they don't build this in ECMAScript...
Seems like a pretty "basic" function to deepclone right?
I guess libraries like lodash made it convenient enough to have that functionality, so there was no reason to provide a native method. Also, since the native types are currently a moving target, it seems prudent to wait until it has stabilized enough to make such a functionality feasible. That shouldn't stop anybody from making your own proposal to the TC39 committee to include
Object.clone(obj)
into a future ECMAScript standard, though.I guess I'll make a proposal myself, if nobody beats me to it. Here's the polyfill:
I also did a small test suite and a documentation. I'll work a bit on it and then release it to the public.
Wow Alex, Your a speedy guy!
Nice work, happy to test with you 👀
Here's the initial draft: github.com/atk/object-clone-proposal. Feedback is appreciated.
Nice article, but be aware of
might not work as expected with JS Date object, for example:
The solution I can think of is instantiating a new Date() and pass the value in the constructor
Ah yes good you mention this, certain objects get destroyed in the JSON conversion!
In this case, you would be better of making a custom deep clone or use one of the tools mentioned.
For more "flat" objects I tend to use JSON.parse, but yes good point!
Love it... great work
Thank you Toqueer, Glad you like it!
Welcome
Great Article. What is the best way to deep copy in vanilla javascript? Thanks
Hey jsforlife,
It does kind of depend on what object you are cloning, I tend to use the
JSON.parse
solution quite often.It's the quickest deep clone and for simple objects works really well.
For my "flat" objects see the comment above dates for instance won't work.
If you are using plugins, stick with the loDash deep clone, that solves it for you.
Great article man,
Have faced this issue plenty of times and have ended with different solutions every time :( and i still don't understand why object.assign() doesn't work
Hey,
Yes you would expect by now the Object.assign would be a deepClone, OR that JavaScript by now would have a perfect DeepClone function build in!