DEV Community

loading...

3 Ways to Clone Objects in JavaScript

samanthaming profile image Samantha Ming Originally published at samanthaming.com ・Updated on ・6 min read

CodeTidbit by SamanthaMing.com

Because objects in #JavaScript are references values, you can't simply just copy using the =. But no worries, here are 3 ways for you to clone an object πŸ‘

const food = { beef: 'πŸ₯©', bacon: 'πŸ₯“' }


// "Spread"
{ ...food }


// "Object.assign"
Object.assign({}, food)


// "JSON"
JSON.parse(JSON.stringify(food))

// RESULT:
// { beef: 'πŸ₯©', bacon: 'πŸ₯“' }

Objects are Reference Types

Your first question might be, whey can't I use =. Let's see what happens if we do that:

const obj = {one: 1, two: 2};

const obj2 = obj;

console.log(
  obj,  // {one: 1, two: 2};
  obj2  // {one: 1, two: 2};
)

So far, both object seems to output the same thing. So no problem, right. But let's see what happens if we edit our second object:

const obj2.three = 3;

console.log(obj2);
// {one: 1, two: 2, three: 3}; <-- βœ…

console.log(obj);
// {one: 1, two: 2, three: 3}; <-- 😱

WTH?! I changed obj2 but why was obj also affected. That's because Objects are reference types. So when you use =, it copied the pointer to the memory space it occupies. Reference types don't hold values, they are a pointer to the value in memory.

If you want to learn more about this, check out Gordon's Zhu Watch and Code course. It's free to enroll and watch the video "Comparison with objects". He gives a super awesome explanation on it.

Using Spread

Using spread will clone your object. Note this will be a shallow copy. As of this post, the spread operator for cloning objects is in Stage 4. So it's not officially in the specifications yet. So if you were to use this, you would need to compile it with Babel (or something similar).

const food = { beef: 'πŸ₯©', bacon: 'πŸ₯“' };

const cloneFood = { ...food };

console.log(cloneFood); 
// { beef: 'πŸ₯©', bacon: 'πŸ₯“' }

Using Object.assign

Alternatively, Object.assign is in the official released and also create a shallow copy of the object.

const food = { beef: 'πŸ₯©', bacon: 'πŸ₯“' };

const cloneFood = Object.assign({}, food);

console.log(cloneFood);
// { beef: 'πŸ₯©', bacon: 'πŸ₯“' }

Using JSON

This final way will give you a deep copy. Now I will mention, this is a quick and dirty way of deep cloning an object. For a more robust solution, I would recommend using something like lodash

const food = { beef: 'πŸ₯©', bacon: 'πŸ₯“' };

const cloneFood = JSON.parse(JSON.stringify(food))

console.log(cloneFood); 
// { beef: 'πŸ₯©', bacon: 'πŸ₯“' }

Lodash DeepClone vs JSON

Here's a comment from the community. Yes, it was for my previous post, How to Deep Clone an Array. But the idea still applies to objects.

Alfredo Salzillo: I'd like you to note that there are some differences between deepClone and JSON.stringify/parse.

  • JSON.stringify/parse only work with Number and String and Object literal without function or Symbol properties.
  • deepClone work with all types, function and Symbol are copied by reference.

Here's an example:

const lodashClonedeep = require("lodash.clonedeep");

const arrOfFunction = [() => 2, {
    test: () => 3,
}, Symbol('4')];

// deepClone copy by refence function and Symbol
console.log(lodashClonedeep(arrOfFunction));
// JSON replace function with null and function in object with undefined
console.log(JSON.parse(JSON.stringify(arrOfFunction)));

// function and symbol are copied by reference in deepClone
console.log(lodashClonedeep(arrOfFunction)[0] === lodashClonedeep(arrOfFunction)[0]);
console.log(lodashClonedeep(arrOfFunction)[2] === lodashClonedeep(arrOfFunction)[2]);

@OlegVaraksin: The JSON method has troubles with circular dependencies. Furthermore, the order of properties in the cloned object may be different.

Shallow Clone vs Deep Clone

When I used spread ... to copy an object, I'm only creating a shallow copy. If the array is nested or multi-dimensional, it won't work. Let's take a look:

const nestedObject = {
  country: 'πŸ‡¨πŸ‡¦',
  {
    city: 'vancouver'
  }
};

const shallowClone = { ...nestedObject };

// Changed our cloned object
clonedNestedObject.country = 'πŸ‡ΉπŸ‡Ό'
clonedNestedObject.country.city = 'taipei';

So we changed our cloned object by changing the city. Let's see the output.

console.log(shallowClone);
// {country: 'πŸ‡ΉπŸ‡Ό', {city: 'taipei'}} <-- βœ…

console.log(nestedObject);
// {country: 'πŸ‡¨πŸ‡¦', {city: 'taipei'}} <-- 😱

A shallow copy means the first level is copied, deeper levels are referenced.

Deep Copy

Let's take the same example but apply a deep copy using "JSON"

const deepClone = JSON.parse(JSON.stringify(nestedObject));

console.log(deepClone);
// {country: 'πŸ‡ΉπŸ‡Ό', {city: 'taipei'}} <-- βœ…

console.log(nestedObject);
// {country: 'πŸ‡¨πŸ‡¦', {city: 'vancouver'}} <-- βœ…

As you can see, the deep copy is a true copy for nested objects. Often time shallow copy is good enough, you don't really need a deep copy. It's like a nail gun vs a hammer. Most of the time the hammer is perfectly fine. Using a nail gun for some small arts and craft is often case an overkill, a hammer is just fine. It's all about using the right tool for the right job πŸ€“

Performance

Unfortunately, I can't write a test for spread because it's not officially in the spec yet. Nevertheless, I included it in the test so you can run it in the future. But the result shows Object.assign is a lot faster than JSON.

Performance Test

Community Input

Object.assign vs Spread

@d9el: It's important to note that Object.assign is a function which modifies and returns the target object. In Samantha's example using the following,

const cloneFood = Object.assign({}, food)

{} is the object that is modified. The target object is not referenced by any variable at that point, but because Object.assign returns the target object, we are able to store the resulting assigned object into the cloneFood variable. We could switch our example up and use the following:

const food = { beef: '🌽', bacon: 'πŸ₯“' };

Object.assign(food, { beef: 'πŸ₯©' });

console.log(food);
// { beef: 'πŸ₯©', bacon: 'πŸ₯“' }

Obviously, the value of beef in our food object is wrong, so we can assign the correct value of beef using Object.assign. We aren't actually using the returned value of the function at all, but we are modifying our target object which we have referenced with the const food.

Spread on the other hand is an operator which copies properties of one object into a new object. If we wanted to replicate the above example using spread to modify our variable food...

const food = { beef: '🌽', bacon: 'πŸ₯“' };

food = {
  ...food,
  beef: 'πŸ₯©',
}
// TypeError: invalid assignment to const `food'

... we get an error, because we use spread when creating new objects, and therefore are assigning a whole new object to food which was declared with const, which is illegal. So we can either choose to declare a new variable to hold our new object in, like the following:

const food = { beef: '🌽', bacon: 'πŸ₯“' };

const newFood = {
  ...food,
  beef: 'πŸ₯©',
}

console.log(newFood);
// { beef: 'πŸ₯©', bacon: 'πŸ₯“' }

or we could declare food with let or var which would allow us to assign a whole new object:

let food = { beef: '🌽', bacon: 'πŸ₯“' };

food = {
  ...food,
  beef: 'πŸ₯©',
}

console.log(food);
// { beef: 'πŸ₯©', bacon: 'πŸ₯“' }

Thanks: @d9el

Deep Clone using External Libraries

  • @lesjeuxdebebel: Personally I use jquery with $.extend(); function
  • @edlinkiii: underscore.js ~~ _.clone()
  • @Percy_Burton: The only way I've known to do this is with the Lodash library, cloneDeep method.

More Ways using JavaScript

  • @hariharan_d3v: Object.fromEntries(Object.entries(food)) [shallow] clones the object.

πŸ’¬ What other ways do you know how to clone an object? Drop them in the comments πŸ™ƒ


Resources


Thanks for reading ❀
Say Hello! Instagram | Twitter | Facebook | Blog | SamanthaMing.com

Discussion (8)

pic
Editor guide
Collapse
nmhillusion profile image
nmhillusion

Also have some issues when clone object with JSON.parse(JSON.stringify)
Can you check?

class Person {
  #name = "";
  constructor(name) {
    this.#name = name;
  }

  greet() {
    console.log("hi, " + this.#name);
  }
}

const p = new Person("Peter");

const obj = { d: new Date(), p };
const newObj = JSON.parse(JSON.stringify(obj));

console.log(typeof obj.d); // output: object
console.log(typeof newObj.d); // output: string

obj.p.greet(); // output: hi, Peter
newObj.p.greet(); // Uncaught TypeError: newObj.p.greet is not a function
Collapse
samanthaming profile image
Samantha Ming Author • Edited

I should of mentioned...the JSON method won’t be able to clone methods 😣

Check out this article, it has more info:
google.ca/amp/s/scotch.io/bar-talk...

Collapse
nmhillusion profile image
nmhillusion

Thank you,
And I have some questions about this,
When I copy:

const obj = {n1: 3, n2: new Number(4)};
const newObj = JSON.parse(JSON.stringify(obj));

console.log(typeof obj.n1); // number
console.log(typeof obj.n2); // object

console.log(typeof newObj.n1); // number
console.log(typeof newObj.n2); // number

obj.n2 and newObj.n2 are the same?

And as in my previous comment, when I copy:

const obj = {d: new Date()};
const newObj = JSON.parse(JSON.stringify(obj));

console.log(typeof obj.d); // object
console.log(typeof newObj.d); // string

Another question, when I create an object by Object.create like this:

const obj = Object.create({}, { "p": {readable: false, value: 11 }});
const newObj = JSON.parse(JSON.stringify(obj));

console.log(obj.p); // 11
console.log(newObj.p); // undefined

Why that, we cannot clone it without readable it? Please help me understand, thank you!

Collapse
patrickjsmirnov profile image
Dmitriy Smirnov

What is difference between spread and object.assign?

Collapse
adameier profile image
adam meier • Edited

I'ts important to note that Object.assign is a function which modifies and returns the target object. In Samantha's example using the following,

const cloneFood = Object.assign({}, food)
Enter fullscreen mode Exit fullscreen mode

{} is the object that is modified. The target object is not referenced by any variable at that point, but because Object.assign returns the target object, we are able to store the resulting assigned object into the cloneFood variable. We could switch our example up and use the following:

const food = { beef: '🌽', bacon: 'πŸ₯“' };

Object.assign(food, { beef: 'πŸ₯©' });

console.log(food);
// { beef: 'πŸ₯©', bacon: 'πŸ₯“' }
Enter fullscreen mode Exit fullscreen mode

Obviously the value of beef in our food object is wrong, so we can assign the correct value of beef using Object.assign. We aren't actually using the returned value of the function at all, but we are modifying our target object which we have referenced with the const food.

Spread on the other hand is an operator which copies properties of one object into a new object. If we wanted to replicate the above example using spread to modify our variable food...

const food = { beef: '🌽', bacon: 'πŸ₯“' };

food = {
  ...food,
  beef: 'πŸ₯©',
}
// TypeError: invalid assignment to const `food'
Enter fullscreen mode Exit fullscreen mode

...we get an error, because we use spread when creating new objects, and therefore are assigning a whole new object to food which was declared with const, which is illegal. So we can either choose to declare a new variable to hold our new object in, like the following:

const food = { beef: '🌽', bacon: 'πŸ₯“' };

const newFood = {
  ...food,
  beef: 'πŸ₯©',
}

console.log(newFood);
// { beef: 'πŸ₯©', bacon: 'πŸ₯“' }
Enter fullscreen mode Exit fullscreen mode

or we could declare food with let or var which would allow us to assign a whole new object:

let food = { beef: '🌽', bacon: 'πŸ₯“' };

food = {
  ...food,
  beef: 'πŸ₯©',
}

console.log(food);
// { beef: 'πŸ₯©', bacon: 'πŸ₯“' }
Enter fullscreen mode Exit fullscreen mode
Collapse
patrickjsmirnov profile image
Dmitriy Smirnov

Got it. Thank you!

Collapse
samanthaming profile image
Samantha Ming Author

Thanks for chiming in and helping answer this question. This is great, let me add it to the code notes πŸ™‚

Collapse
evolutionxbox profile image
Jonathan Cousins

I don't think there is a difference aside from syntax in this case. The triple dot expression ... also means rest which does not mean Object.assign.

For more information see 2ality.com/2016/10/rest-spread-pro...