
Methods for deep cloning objects in JavaScript
Methods for deep cloning objects in JavaScript 관련

Introduction
In JavaScript, objects are like a store or collection of key-value pairs. They are a kind of structural data type, which can be seen as a collection of properties. These properties can either be values of other data types, including primitive types like Boolean
, Number
, undefined
, etc., or even other objects. Therefore, with objects, we can build even more complex data structures.

Due to the nature of objects in JS, they are usually stored in memory and can only be copied by reference. This means that a variable does not store an object in itself, but rather an identifier, which represents an address or a reference to that particular object in memory. As such, objects cannot be treated in the same way as primitives.
With primitive data types, once variables are assigned, they cannot be copied over. Therefore, changing the value of the variable never changes the underlying primitive type. This means it is impossible to change the values of these types once they are assigned to a variable — a concept known as immutability. However, they can be combined together to derive new values.
Objects, on the other hand, are mutable data types. In this article, we will explore ways of modifying or mutating objects in JavaScript. This entails performing either shallow or deep cloning or copying with respect to general object behavior.
Introducing object behavior
To reiterate, objects are reference types, and as such, when we copy an object variable, we are indirectly creating one more reference to the same object stored somewhere else in the computer’s memory. Therefore, when an object variable is copied, only a reference to the object is copied — the actual object is not!
Let’s look at an example to understand this concept better:
let user = { name: "Alexander" }
// this instead copies a reference to the previous object
let newUser = user
In the above example, we have two variables, each making reference to the same object in memory. In this case, the variable newUser
has a reference to the initially declared user
variable in memory. Note that this is only possible for reference types like objects and arrays; for primitive types like a string or a Boolean, this is not the case.
Note
We can make use of the Object.is()
method to determine whether the two values are actually the same value. Running console.log(Object.is(user, newUser))
on the browser console should return the Boolean value true
.
Object copying methods
JavaScript offers many ways of copying objects, but they do not provide a deep copy. Performing shallow copies is the default behavior in most of the cases.
We should note that ES6 provides two shorter syntaxes for shallow copying objects in the language. They include Object.assign()
` and the spread syntax, which copies values of all enumerable own properties. from one object to another.
Note
A shallow copy successfully copies primitive types like numbers and strings, but any object reference will not be recursively copied, but instead the new, copied object will reference the same initial object.
Let’s look at them one after the other:
Copying an object with the Object.assign()
method
Among the object constructor methods, Object.assign()
is used to copy the values and properties from one or more source objects to a target object. It returns the target object, which has properties and values copied from the source object.
Since Object.assign()
copies property values, it is unsuitable for deep cloning. Basically, we can use this method for shallow cloning an object and for merging two or more objects into one bigger object with the same properties.
Syntax
const copied = Object.assign(target, ...sources)
Note
In using this method, if we have matching keys in both target and source objects, the matching keys in the second object would override the first one after cloning.
Parameters
target
– target object to which values and properties are copiedsources
– source object from which values and properties are copied
Return value
- This method returns the target object.
Now, let’s look at a very simple example of using this method to merge two objects together:
let objectA = {a: 1, b: 2}
let objectB = {c: 3, d: 4}
Object.assign(objectA, objectB)
console.log(objectA);
// → { a: 1, b: 2, c: 3, d: 4 }
Here, the target object is objectA
, while the source object is objectB
. Using object.assign()
is similar to using the lodash clone
method for shallow copying objects. Let’s look at another example:
const clone = require('lodash.clone')
var objA = {
a: 1,
b: {
c: 2,
d: {
e: 3
}
}
}
var objB = clone(objA)
objA.b.c = 30
console.log(objA)
// { a: 1, b: { c: 30, d: { e: 3 } } }
console.log(objB)
// { a: 1, b: { c: 30, d: { e: 3 } } }
Being a shallow copy, values are cloned and object references are copied —not the objects themselves. So if we edit an object property in the original object, it is also modified in the copied object since the referenced inner object is the same in this case.
Copying an object with the spread syntax
The spread operator (tc39/proposal-object-rest-spread
) is an ES2018 feature that adds spread properties to object literals. It provides a very convenient way to perform a shallow clone, equivalent to what Object.assign()
does. With objects, the spread operator is used to create copies of existing objects with new or updated values.
It copies enumerable properties from a provided object onto a new object. Let’s see an example usage, as per the syntax:
const copied = { ...original }
Now let’s look at a real-world example:
const objA = {
name: 'Alexander',
age: 26,
}
const objB = {
Licensed: true,
location: "Ikeja"
}
const mergedObj = {...objA, ...objB}
console.log(mergedObj)
// { name: 'Alexander', age: 26, Licensed: true, location: 'Ikeja' }
From the above, we can see that mergedObj
is a copy of objA
and objB
. Actually, every enumerable property on the objects will be copied to the final mergedObj
object. The spread operator is just a shorthand for the Object.assign()
method, but there are some subtle differences between the two, including the fact that Object.assign()
triggers setters
, whereas the spread operator does not.
Note
If an object references other objects when performing a shallow copy of the object, we copy the references to the external object. When performing a deep copy, those external objects are copied as well, so the new cloned object is completely independent from the old one.
Recommended method for deep cloning objects in JavaScript
Most of the time, when we decide to copy objects in our program, our intent is to actually copy by reference, which is more or less making a shallow copy of the object. However, when it comes to deeply nested objects, the behavior of Object.assign()
or spread
is different.
In essence, there is no one consistent way of cloning or copying objects in the language, regardless of their structure, in terms of how the objects are constructed.
A question that arises here is copying deeply nested objects up to, say, two or three levels deep in such a way that if we make changes to the new object, it doesn’t affect the original object acting as our target. So how do we correctly deep clone an object?
To perform a deep copy, our best bet is to rely on a library that’s well tested, popular, and well maintained by the community: Lodash. Lodash offers both clone
and cloneDeep
functions to perform shallow and deep cloning, respectively.
For example, when deep copying objects in Node.js, we can make use of the Lodash cloneDeep()
method. An example is shown below:
const cloneDeep = require('lodash.clonedeep')
let objA = {
a: 1,
b: {
c: 2,
d: {
e: 3
}
}
}
// copy objA save as new variable objB
let objB = cloneDeep(objA)
// change the values in the original object objA
objA.a = 20
objA.b.c = 30
objA.b.d.e = 40
console.log(JSON.stringify(objA))
// → {"a":20,"b":{"c":30,"d":{"e":40}}}
// objB which is the cloned object is still the same
console.log(JSON.stringify(objB))
// → {"a":1,"b":{"c":2,"d":{"e":3}}}
The Lodash cloneDeep()
method is similar to clone
, except that it recursively clones value
while preserving object inheritance. The great thing about the library is that we can import each function individually — no need to import the entire library into our project. This can greatly reduce the size of our program dependencies.
In order to make use of Lodash clone methods in Node.js, we can install it by running npm i lodash.clonedeep
for deep clone and npm i lodash.clone
for shallow clone. We can use it like so:
const clone = require('lodash.clone')
const cloneDeep = require('lodash.clonedeep')
const shallowCopy = clone(originalObject)
const deepCopy = clonedeep(originalObject)
Note
Copying objects derived from built-in JavaScript objects will result in extra, unwanted properties.
Native deep cloning
The HTML standard includes an internal structured cloning/serialization algorithm that can create deep clones of objects. Although still limited to certain built-in types, it can preserve references within the cloned data, allowing support for cyclical and recursive structures that would otherwise cause errors with JSON.
With support in Node.js still experimental, the v8
module exposes the structured serialization API directly. For example, cloning an object is as simple as:
const v8 = require('v8');
const structuredClone = obj => {
return v8.deserialize(v8.serialize(obj));
};
Other object cloning methods
Iterating through each object property and copying it into a new empty object
This involves iterating through a source object’s properties and copying all of them one after the other to a target object. The idea is to create a new object and replicate the structure of the existing one by iterating over its properties and copying them.
Let’s see an example:
let user = {
name: "Alexander",
age: 26
};
let clone = {}; // the new empty object
// let's copy all user properties into it
for (let key in user) {
if (user.hasOwnProperty(key)) {
clone[key] = user[key];
}
}
// now clone is a fully independent object with the same content
clone.name = "Chinedu"; // changed the data
console.log(user.name); // still Alexander in the original object
Cloning Objects using JSON.parse/stringify
This offers a very fast way of deep cloning objects. However, it is not very reliable and standard as it comes with some data loss along the way.
Using this method, the source object must be JSON-safe. If we do not use Date
, undefined
, Infinity
, functions, regexps, maps, sets, or other complex types within our object, a very simple way to deep clone an object is by using:
JSON.parse(JSON.stringify(object))
Let’s look at an example:
const a = {
string: 'string',
number: 123,
bool: false,
nul: null,
date: new Date(), // string
undef: undefined, // lost
inf: Infinity, // 'null'
re: /.*/, // lost
}
console.log(typeof a.date) // returns object
const clone = JSON.parse(JSON.stringify(a))
console.log(typeof clone.date) // returns string
console.log(clone)
//
// {
// string: 'string',
// number: 123,
// bool: false,
// nul: null,
// date: '2020-09-28T15:47:23.734Z',
// inf: null,
// re: {}
// }
Note
This method needs some sort of exception handling to keep it safe in case the source object can not be converted to JSON.
Conclusion
By default, JavaScript always passes by value, which means changing the value of the variable never changes the underlying primitive type. However, for non-primitive data types (arrays, functions, and objects), which are passed by reference, we can always mutate the data, causing a single object value to have different content at different times.
Cloning a JavaScript object is a task that is used mostly because we do not want to create the same object if it already exists. As we are now aware, objects are assigned and copied by reference. In other words, a variable stores not the object value, but a reference. Therefore, copying such a variable or passing it as a function argument copies that reference, not the object.
For simple objects that only store primitive types like numbers and strings, shallow copying methods discussed earlier will work. A shallow copy means the first level is copied, and deeper levels are referenced. However, if the object properties reference other nested objects, the actual object won’t be copied, as we would only be copying the reference.
