Cloning an object in JavaScript means creating a new object with the same properties as the original object. Objects in JavaScript are stored by reference, which means that updating one object variable can impact other variables.
const userOne = { name: 'John', age: 31 }; const userTwo = userOne; userTwo.name = 'Sam'; console.log(userOne.name); // Sam, because userOne shares the same reference with userTwo
The main goal of cloning an object is to copy data so we can modify the object without affecting the original object. This is particularly useful when working with tools that rely on immutability like NgRx or Redux.
Based on the object we want to clone, it is important to choose the right method. Here are 3 different ones, plus a bonus one in the end.
Spread operator or Object.assign()
This first method is really fast, but only allows shallow cloning, which means that it is not recursive, and won't clone nested objects.
const userOne = { name: 'John', age: 31, settings: { autoSave: true, keyboardNavigation: false } }; const userTwo = { ...userOne }; // or Object.assign({}, userOne) userTwo.name = 'Sam'; console.log(userOne.name); // John 👍 userTwo.settings.autoSave = false; console.log(userOne.settings.autoSave); // false: the value of the original object has changed 👎
So we can use this one when we know that the original object does not have nested objects. For deep cloning, we can jump to the next section with JSON.stringify().
JSON.stringify()
This method consists in creating a new variable with JSON.stringify() and then parse it to get a new object.
const userOne = { name: 'John', age: 31, settings: { autoSave: true, keyboardNavigation: false }, registrationDate: new Date('2021-12-28') }; const userTwo = JSON.parse(JSON.stringify(userOne)); userTwo.name = 'Sam'; console.log(userOne.name); // John 👍 userTwo.settings.autoSave = false; console.log(userOne.settings.autoSave); // true 👍
But this method has some limitations: if the original object store properties like Date, functions, undefined, Infinity, RegExps, Maps, Sets, Blobs, Typed Arrays or other complex types, those data will be lost, because it will be converted into string.
console.log(typeof userOne.registration); // date object 👍 console.log(typeof userTwo.registration); // string 👎
So we can use this method if we have nested objects, but storing only primitive values and arrays. If we have more complex objects to clone, we can get the help of an external library that provides a tool for this: cloneDeep() from lodash.
Lodash cloneDeep
Lodash cloneDeep() function is a much more robust deep clone than JSON.parse(JSON.stringify()). It handles many common edge cases, like dates, and contrary to spread operator, it also handles recursivity and nested objects.
We can use it like this:
const userOne = { name: 'John', age: 31, settings: { autoSave: true, keyboardNavigation: false } }; const userTwo = _.cloneDeep(userOne);
This method can also be imported separately via the lodash.clonedeep module and this is what I would recommend, if we do not need the entire lodash library.
So cloneDeep can be your best choice if you have complex objects to handle, but it comes with a heavy performance cost, which I will detail in a future post. If you know that you can use one of the previous two methods, then go for it.
Bonus: native cloning with structuredClone
The structuredClone global function will soon be provided by all major browsers. It will look like this:
const clone = structuredClone(original);