Different methods of copying objects in Javascript

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.

References


Share on social media

//