When we work with JavaScript frameworks, cloning objects is one of the most recurring activities we do, and if we don't do it efficiently, or in the most correct manner for each case, our application might not work as intended.
The short answer to how to clone objects is simply to use Object.assign({}, obj), for a shallow copy and JSON.parse(JSON.stringify(obj)) for a deep clone of the object.
But the full answer goes a little further and is hard to put into just one sentence. Let me show you:
The best method in my opinion
The best method is when you know the structure of the object (99% of cases I would say), and can make a simple for()
in its properties and start filling the object in its way if performance is something fundamental.
It is also important to know the difference between shallow and deep clone (See below) to choose the best method.
If you want to clone an object that contains only strings and numbers, I recommend using Object.assign()
, see below in "Shallow clone and its most efficient method".
But for a fast and efficient generic deep clone, the following method I found on the internet in a JavaScript benchmark. Possibly this is the author: https://github.com/c7x43t
The original method did not include date support, so I modified the function a bit by adding this support.
The following method is then compatible with objects that contain objects, arrays, functions, dates, null, strings and numbers.
See the performance comparison of this function here: <href="http://jsben.ch/ligM2" target="_blank">http://jsben.ch/ligM2
As the image above shows, the deepClone function takes only 30% of the time of the stringify + parse method.
Here is the deepClone method:
const deepClone = obj => {
// If it's not an array or an object, returns null
if (typeof obj !== 'object' || obj === null) {
return obj;
}
let cloned, i;
// Handle: Date
if (obj instanceof Date) {
cloned = new Date(obj.getTime());
return cloned;
}
// Handle: array
if (obj instanceof Array) {
let l;
cloned = [];
for (i = 0, l = obj.length; i < l; i++) {
cloned[i] = deepClone(obj[i]);
}
return cloned;
}
// Handle: object
cloned = {};
for (i in obj) if (obj.hasOwnProperty(i)) {
cloned[i] = deepClone(obj[i]);
}
return cloned;
}
const obj = { "name": "Ricardo Metring", "subobj": { "color": "black" } };
const obj2 = deepClone(obj);
Shallow vs Deep Clone
In any programming language, to clone a simple variable that contains an integer, we just have to assign a value to the new variable. For instance: var a = b;
.
However, when working with more complex data structures, such as a JavaScript object, just assigning the value to the new variable results in copying only the reference from the memory for that variable. In other words, if we change the "copied" object, we are also changing the object in the previous variable. You can try it out:
let obj1 = { "color": "grey" };
let obj2 = obj1;
obj2.color = "white";
console.log(obj1.color, obj2.color); // white white
In order to clone an object "a" in an object "b", so that we can change the object "b" without modifying the object "a", we need to do cloning, and this cloning can be either shallow or deep. But what is the difference between shallow and deep clone, anyway? In fact, the name itself already indicates what each one means, and to understand the difference, we need an object with more than one level of depth.
Shallow clone and its most efficient method
In shallow cloning, only the first level of the object's attributes are actually cloned. Objects within it are not cloned, only the references are copied.
This means that if I change any "depth 2" attribute, or deeper, I'm also changing the attribute of the original object. Confused? See the code below for a better understanding.
Object.assign
If you only need to clone an object that does not contain other branches, such as functions, objects or arrays, I recommend using Object.assign().
const card = {
"id": "1234567",
"user": {
"name": "Ricardo Metring"
}
};
// Here we do a shallow copy of the variable card
const card2 = Object.assign({}, card);
// Now we change the contents of the object to test it
card2.id = "2222222";
card2.user.name = "John Doe";
// When printing out the values, note that card.user.name was also changed
console.log(card.id); // 1234567
console.log(card.user.name); // John Doe
console.log(card2.id); // 2222222
console.log(card2.user.name); // John Doe
spread Operator (...)
The spread operator is a feature of ES6 that came to make our lives much easier. This operator alone deserves a dedicated article here on the website, as it is useful for many things, but one of its main uses is to merge objects.
We can then make a shallow copy with the spread operator as follows:
const obj2 = { ...obj };
Basically, in the above code we are creating a new object and telling JavaScript to list all the properties of the obj variable within this new object.
This method was faster than Object.assign in my benchmarking tests, but since it is a newer version of JS it is not compatible with some old browsers, like Opera and Internet Explorer, so for the simplicity of Object.assign it's basically the same thing if you use one or the other.
Deep clone and other methods
When deep cloning, we get a new copy of the object, along with its arrays, functions, or internal objects that are all like new variables.
There are several methods to create a deep copy of an object in JavaScript, but most of them require some dependency.
My recommended method for the deep clone is further up in the title The best method in my opinion.
Let me show you some other simpler methods:
jQuery
If you already include jQuery in your dependencies, you can use the extend()
method. Remember you must use the true flag to make the deep clone. Do not confuse this method with the clone()
of jQuery which is only for DOM elements.
const obj = { "name": "Ricardo Metring", "num": 22 };
const obj2 = jQuery.extend(true, {}, obj);
lodash clone
As with jQuery, I only recommend using this method if you already include the lodash library in your project.
const obj = { "name": "Ricardo Metring", "num": 22 };
const obj2 = _.clone(obj, true);
JSON stringify and JSON parse
This is a widely used method, as it requires no external dependencies. It works in a way that in my opinion seems a bit ugly, but still ok to use. In this benchmark, however, it turns out to be the slowest for me in Chrome, but one of the fastest in Firefox. (???)
It is important to note that this method is not compatible with functions or dates, because JSON is not compatible with functions, and dates are converted to string by JSON.stringify.
const obj = { "name": "Ricardo Metring", "num": 22 };
const obj2 = JSON.parse(JSON.stringify(obj));
Did you scroll all the way for the Ctrl + C? It's in "The best method in my opinion".
If you read so far, thank you. You already know everything about cloning objects in JavaScript!
See you later!