How to clone an object in JavaScript?

This seemingly straightforward question has a surprisingly high number of intricacies.

Imagine you need to obtain a copy of the following object.

const user = {
  name: 'John',
  address: { city: 'Montreal' }
}

Should we simply create a new variable? (no)

If we assign our object to a new variable, it hardly helps.

const newUser = user

newUser.name = "Anna"
console.log(newUser.name) //=> Anna
console.log(user.name) //=> Anna

Both objects change.

And the reason is simple - we did not create a new object. We simply made a new reference.

There are now two variables: user and newUser, which both refer to the same object.

Shallow clone

When to use it: when your object is only one level deep, or you do not care about the nested references.

Shallow cloning works only if the source object doesn't contain any references to other objects.

const newUser = {...user}

newUser.name = "Anna"
console.log(newUser.name) //=> Anna
console.log(user.name) //=> John, yay!

newUser.address.city = "Paris"
console.log(newUser.address.city) //=> Paris
console.log(user.address.city) //=> Paris

Updating the city results in changing the original address as well, and this is because newUser.address and user.address still refer to the same object in memory.

This is why it is called "shallow" clone: it only copies the top layer.

In our example, we used the spread operator ... to perform a shallow clone, but we could as well use Object.assign.

const newUser = Object.assign({}, user)

Deep clone by converting to JSON and back

When to use it: when you need a deep clone, and your object only contains arrays, primitives, and other plain objects.

One popular way to clone an object is to convert it into a JSON string and then parse it.

const newUser = JSON.parse(JSON.stringify(user))

newUser.name = "Anna"
console.log(newUser.name) //=> Anna
console.log(user.name) //=> John, yay!

newUser.address.city = "Paris"
console.log(newUser.address.city) //=> Paris
console.log(user.address.city) //=> Montreal, yay!

While it works for most cases, it still has its limitations. It only correctly converts plain objects.


// Date objects turn into strings
JSON.parse(JSON.stringify({ now: new Date() }))
// {now: '2022-07-14T13:21:36.761Z'}

// keys with undefined values disappear
JSON.parse(JSON.stringify({"test": undefined}))
//=> {}

// can't clone a Set
JSON.parse(JSON.stringify({ set: new Set([1, 2, 3]) }))
//=> { set: {} }

// nor a Symbol
JSON.parse(JSON.stringify({ num: Symbol("test") }))
//=> {}

// nor a regex
JSON.parse(JSON.stringify({ re: /abc/i }))
//=> {}

// nor a BigInt
JSON.parse(JSON.stringify({ num: 1n }))
//=> Uncaught TypeError: Do not know how to serialize a BigInt

Recursive clone

When to use it: when your object contains complex structures like Maps, RegEx-es, etc.

The structuredClone can solve most of those issues.

It works, as you might expect, by recursively traversing an object. It is clever enough to copy object with cyclic references and knows how to clone an extensive list of different types (including, Maps, Sets, Dates, and more).

structuredClone({re: /abc/i, set: new Set([1, 2, 3])})
//=> {re: /abc/i, set: Set(3) {1, 2, 3}}

It still has its limitations: for example, it can't clone functions or DOM nodes.

The nice thing is that it's already supported by every major browser.

Note that it's not part of the ECMAScript standard but part of the browser API, and the implementation might differ from one browser to another. Node.js doesn't have it at all.

Alternatively, we can always use lodash's cloneDeep, which implements a similar algorithm.

Conclusion

Picking a specific cloning mechanism depends on the situation. While I believe, in most cases, JSON.parse(JSON.stringify()) should do the work, sometimes you might wanna use structuredClone function to copy complex objects. Know your data before choosing a cloning mechanism.

Where to go next?

🔥 100+ questions with answers
🔥 50+ exercises with solutions
🔥 ECMAScript 2023
🔥 PDF & ePUB