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.
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. Runningconsole.log(Object.is(user, newUser))
on the browser console should return the Boolean valuetrue
.
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:
Object.assign()
methodAmong 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.
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.
target
– target object to which values and properties are copiedsources
– source object from which values and properties are copiedNow, 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.
The spread operator 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.
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.
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)); };
More details can be found here.
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
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.
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.
Debugging code is always a tedious task. But the more you understand your errors, the easier it is to fix them.
LogRocket allows you to understand these errors in new and unique ways. Our frontend monitoring solution tracks user engagement with your JavaScript frontends to give you the ability to see exactly what the user did that led to an error.
LogRocket records console logs, page load times, stack traces, slow network requests/responses with headers + bodies, browser metadata, and custom logs. Understanding the impact of your JavaScript code will never be easier!
Hey there, want to help make our blog better?
Join LogRocket’s Content Advisory Board. You’ll help inform the type of content we create and get access to exclusive meetups, social accreditation, and swag.
Sign up nowLearn how to implement one-way and two-way data binding in Vue.js, using v-model and advanced techniques like defineModel for better apps.
Compare Prisma and Drizzle ORMs to learn their differences, strengths, and weaknesses for data access and migrations.
It’s easy for devs to default to JavaScript to fix every problem. Let’s use the RoLP to find simpler alternatives with HTML and CSS.
Learn how to manage memory leaks in Rust, avoid unsafe behavior, and use tools like weak references to ensure efficient programs.
One Reply to "Methods for deep cloning objects in JavaScript"
‘This can greatly reduce the size of our program dependencies.’ — Even I was thinking the same until I read this in their website. https://lodash.com/per-method-packages