Different methods of copying objects in Javascript
In this article, you will learn different methods to copy an object in Javascript. It is no news that JavaScript —which happens to be my favorite programming language possesses some weirdness. One of which is the way objects are copied.
Take a look the following code snippet:
let name = 'james'
let secondName = name;
name = 'john';
console.log(secondName) // james
Everything looks normal right?
I’m sure you must be wondering … what does he want to show us?
Well, take a look at something similar in the next code block:
let myObj = {
name: 'James'
}
let secObj = myObj;
myObj.name = 'John'
console.log(secObj.name) // John
Changing the value of myObj.name
here caused the value of secObj.name
to change as well. This is odd behaviour, but it is expected since javascript passes objects by reference.
What this means is in the second code block, when myObj
was created, javascript created a location in memory for the object, and myObj
contains a reference to the object that got created. Hence, in line 4 when secObj
is created and set to the value of myObj
, secObj
is now passed the reference to the object. No new object gets created in the process. and both myObj
and secObj
refer to the same object. So, editing myObj.x
will change the value of x
and since secObj
refers to that same object, secObj.x
will get changed as well.
Well, this poses a great challenge because it means copying objects in javascript will not work. Since changing the value of the property of one object will be reflected in the other object.
So, what are the Different methods of copying objects in Javascript? Let’s find out:
Object Spread
The Object spread … is one of the new features that came to the language in ES6. You can learn more about it here.
Using it to copy objects is probably the easiest method of copying objects.
let myObj = {
name: 'James'
}
// copy myObj using spread syntax
let secObj = {...myObj};
myObj.name = 'John'
console.log(secObj.name) // James
It should be noted however that this method of copying objects only work on 1 level of nesting.
Hence, spread syntax does shallow copying of objects.
let myObj = {
name: 'James',
school: {
name: 'monef',
class: 'ss3'
}
}
let secObj = {...myObj};
myObj.school.class = 'jss2'
myObj.name = 'John';
console.log(secObj) // {name:'James',school:{name:'monef',class:'jss2'}}
From the snippet above, it can be seen that while the value of secObj.name
does not change even after modifying myObj.name
, the change in myObj.school.class
, caused a change in secObj.school.class
.
So, while this works, it just does a shallow copy of the object. You should use this if you are sure the content of the object isn’t nested. Or if you want the nested part of the object to be constant.
Object.assign
The Object.assign method as defined on MDN is used to copy the values of all enumerable own properties from one or more source objects to a target object.
Looking at the code snippet below,
const target = { a: 1, b: 2 };
const source = { b: 4, c: 5 };
const returnedTarget = Object.assign(target, source);
console.log(target); // { a: 1, b: 4, c: 5 }
console.log(returnedTarget); //{ a: 1, b: 4, c: 5 }
Object.assign copied the contents of source
and merged it with the content of target
, and stored the merged object in target
and also returned it, hence returnedTarget
contains the same object.
To use this to copy objects, we can replace target
with the object literal {}
, then a new object containing all the values from the source
object will be returned:
let myObj = {
name: 'James'
}
let secObj = Object.assign({}, myObj);
myObj.name = 'John';
console.log(secObj.name); // James
As seen in the snippet above, secObj
was created using Object.assign
, but with an empty object as the target, which all the properties from myObj
got copied to.
While this is a seemingly intelligent solution to copy objects, it also does a shallow copy of the object, hence, nested objects won’t be copied, by instead passed by reference.
JSON.stringify and JSON.parse
So far, all the methods for copying objects we have looked at does a shallow copy of the object. Combining JSON.stringify
and JSON.parse
seems to do the trick?JSON.stringify
converts a javascript object to a JSON string, while JSON.parse
converts a JSON string to a javascript object.
Intelligently, these can be combined to provide a solution that can deep copy an object. If we JSON.stringify
an object, it becomes a new string and loses the reference to its parent object. JSON.parsing
this new string will, therefore, create an object that has lost all ties to its roots, hence, it is an independent object.
let myObj = {
name: 'James',
school: {
name: 'monef',
class: 'ss3'
}
}
let secObj = JSON.parse(JSON.stringify(myObj));
myObj.school.class = 'jss2'
myObj.name = 'okoro';
console.log(secObj) // { name: 'James', school: { name: 'monef', class: 'ss3' } }
As seen above, the properties of secObj
remains constant even after changing myObj
.
It should be noted however that if the source object, in this case myObj
contains a method, that method won’t be copied.
let myObj = {
name: 'James',
school: {
name: 'monef',
class: 'ss3'
},
dance() {
console.log('dance')
}
}
let secObj = JSON.parse(JSON.stringify(myObj));
myObj.school.class = 'jss2'
myObj.name = 'okoro';
// secObj does not contain the dance function.
console.log(secObj) // { name: 'James', school: { name: 'monef', class: 'ss3' } }
This method also does similar for properties in the source object whose values are undefined or symbols. This is because JSON.stringify
converts the object to a string, but these can’t be represented as strings.
Conclusion
Choosing the best way to copy objects should be easier since we now understand the way these are done. under the hood. You can decide to stick to these implementations or you write yours. There are other solutions provided by more popular libraries like lodash, underscore.
Thanks.