How to deep clone an object in JavaScript
There is a built-in for this now — structuredClone. The question was asked in
2008 and got the answer it deserved at the time; the interesting part today is
what each option quietly does to your data.
Why a copy is not a copy
const shallow = { ...src };
shallow.nested.a[0] = "CHANGED";
after editing the copy, the ORIGINAL says: CHANGED
Spread and
Object.assign
copy the top level. Nested objects are copied as references, so both “copies”
point at the same thing.
structuredClone
const c = structuredClone(src);
original after editing the clone: 1
structuredClone
is a global in browsers and in Node. One call, no dependency.
What the JSON round-trip destroys
JSON round-trip:
date String "2020-01-01T00:00:00.000Z"
map Object {}
set Object {}
regexp Object {}
typed Object {"0":1,"1":2,"2":3}
undef KEY GONE undefined
inf object null
nan object null
structuredClone:
date Date Wed Jan 01 2020 00:00:00 GMT+0000
map Map [object Map]
set Set [object Set]
regexp RegExp /ab+c/gi
bigint BigInt 10
typed Uint8Array 1,2,3
undef undefined undefined
inf Number Infinity
nan Number NaN
JSON.parse(JSON.stringify(x)) is the answer that still gets pasted everywhere,
and this is what it does. A Date comes back as a string that no longer has
.getTime(). A Map and a Set come back as {} — the contents are simply
gone, with no error. NaN and Infinity become null, and a key holding
undefined disappears entirely, so "k" in obj changes answer.
None of that throws. Two things do:
BigInt : TypeError: Do not know how to serialize a BigInt
circular ref : TypeError: Converting circular structure to JSON
structuredClone handles the circular case and keeps the identity:
structuredClone: ok, and cc.self === cc is true
What structuredClone drops
a function : DOMException: () => 1 could not be cloned.
a class : x = 1 , constructor = Object , typeof foo.hello = undefined
a prototype : own = 2 , inherited 'proto' = undefined
a getter : becomes a plain value: 42
A function throws, loudly, which is fine. The second line is the one to
remember: cloning a class instance does not throw. You get a plain object
with the fields and no methods, and constructor.name is "Object". The
failure surfaces later, somewhere else, as “is not a function”.
Getters are flattened to whatever they returned at clone time, and the prototype chain is not followed.
The “efficient” in the question
JSON round-trip 7.87 ms
structuredClone 9.40 ms
10,000 small plain objects — JSON’s best case, and it wins. That is one shape of
data and not a general ranking, but it is enough to retire the idea that
structuredClone is chosen for speed. It is chosen because it is correct on
data the JSON round-trip mangles.
If the object really is plain JSON-shaped data and you are cloning it in a hot loop, the old trick is still defensible. Everywhere else, the losses above are the reason not to.
Where it is not available
structuredClone needs Node 17 or a 2022-era browser. Below that it is
lodash.cloneDeep or the JSON round-trip
with its losses accepted on purpose.
About Netcup (advertisement)
The German host Netcup offers, among other things, affordable and powerful web hosting packages, KVM-based root servers and dedicated servers. With our voucher codes you can save even more (6€ off your first order, 30% off all KVM-based root servers, ...).