[javascript] Is this a good way to clone an object in ES6?

Googling for "javascript clone object" brings some really weird results, some of them are hopelessly outdated and some are just too complex, isn't it as easy as just:

let clone = {...original};

Is there anything wrong with this?

This question is related to javascript ecmascript-6 javascript-objects

The answer is


You can do it like this as well,

let copiedData = JSON.parse(JSON.stringify(data));

All the methods above do not handle deep cloning of objects where it is nested to n levels. I did not check its performance over others but it is short and simple.

The first example below shows object cloning using Object.assign which clones just till first level.

_x000D_
_x000D_
var person = {_x000D_
    name:'saksham',_x000D_
    age:22,_x000D_
    skills: {_x000D_
        lang:'javascript',_x000D_
        experience:5_x000D_
    }_x000D_
}_x000D_
_x000D_
newPerson = Object.assign({},person);_x000D_
newPerson.skills.lang = 'angular';_x000D_
console.log(newPerson.skills.lang); //logs Angular
_x000D_
_x000D_
_x000D_

Using the below approach deep clones object

_x000D_
_x000D_
var person = {_x000D_
    name:'saksham',_x000D_
    age:22,_x000D_
    skills: {_x000D_
        lang:'javascript',_x000D_
        experience:5_x000D_
    }_x000D_
}_x000D_
_x000D_
anotherNewPerson = JSON.parse(JSON.stringify(person));_x000D_
anotherNewPerson.skills.lang = 'angular';_x000D_
console.log(person.skills.lang); //logs javascript
_x000D_
_x000D_
_x000D_


We can do that with two way:
1- First create a new object and replicate the structure of the existing one by iterating 
 over its properties and copying them on the primitive level.

let user = {
     name: "John",
     age: 30
    };

    let clone = {}; // the new empty object

    // let's copy all user properties into it
    for (let key in user) {
      clone[key] = user[key];
    }

    // now clone is a fully independant clone
    clone.name = "Pete"; // changed the data in it

    alert( user.name ); // still John in the original object

2- Second we can use the method Object.assign for that 
    let user = { name: "John" };
    let permissions1 = { canView: true };
    let permissions2 = { canEdit: true };

    // copies all properties from permissions1 and permissions2 into user
    Object.assign(user, permissions1, permissions2);

  -Another example

    let user = {
      name: "John",
      age: 30
    };

    let clone = Object.assign({}, user);
It copies all properties of user into the empty object and returns it. Actually, the same as the loop, but shorter.

But Object.assign() not create a deep clone

let user = {
  name: "John",
  sizes: {
    height: 182,
    width: 50
  }
};

let clone = Object.assign({}, user);

alert( user.sizes === clone.sizes ); // true, same object

// user and clone share sizes
user.sizes.width++;       // change a property from one place
alert(clone.sizes.width); // 51, see the result from the other one

To fix that, we should use the cloning loop that examines each value of user[key] and, if it’s an object, then replicate its structure as well. That is called a “deep cloning”.

There’s a standard algorithm for deep cloning that handles the case above and more complex cases, called the Structured cloning algorithm. In order not to reinvent the wheel, we can use a working implementation of it from the JavaScript library lodash the method is called _.cloneDeep(obj).


if you don't want to use json.parse(json.stringify(object)) you could create recursively key-value copies:

function copy(item){
  let result = null;
  if(!item) return result;
  if(Array.isArray(item)){
    result = [];
    item.forEach(element=>{
      result.push(copy(element));
    });
  }
  else if(item instanceof Object && !(item instanceof Function)){ 
    result = {};
    for(let key in item){
      if(key){
        result[key] = copy(item[key]);
      }
    }
  }
  return result || item;
}

But the best way is to create a class that can return a clone of it self

class MyClass{
    data = null;
    constructor(values){ this.data = values }
    toString(){ console.log("MyClass: "+this.data.toString(;) }
    remove(id){ this.data = data.filter(d=>d.id!==id) }
    clone(){ return new MyClass(this.data) }
}

If the methods you used isn't working well with objects involving data types like Date, try this

Import _

import * as _ from 'lodash';

Deep clone object

myObjCopy = _.cloneDeep(myObj);

EDIT: When this answer was posted, {...obj} syntax was not available in most browsers. Nowadays, you should be fine using it (unless you need to support IE 11).

Use Object.assign.

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/assign

var obj = { a: 1 };
var copy = Object.assign({}, obj);
console.log(copy); // { a: 1 }

However, this won't make a deep clone. There is no native way of deep cloning as of yet.

EDIT: As @Mike 'Pomax' Kamermans mentioned in the comments, you can deep clone simple objects (ie. no prototypes, functions or circular references) using JSON.parse(JSON.stringify(input))


Following on from the answer by @marcel I found some functions were still missing on the cloned object. e.g.

function MyObject() {
  var methodAValue = null,
      methodBValue = null

  Object.defineProperty(this, "methodA", {
    get: function() { return methodAValue; },
    set: function(value) {
      methodAValue = value || {};
    },
    enumerable: true
  });

  Object.defineProperty(this, "methodB", {
    get: function() { return methodAValue; },
    set: function(value) {
      methodAValue = value || {};
    }
  });
}

where on MyObject I could clone methodA but methodB was excluded. This occurred because it is missing

enumerable: true

which meant it did not show up in

for(let key in item)

Instead I switched over to

Object.getOwnPropertyNames(item).forEach((key) => {
    ....
  });

which will include non-enumerable keys.

I also found that the prototype (proto) was not cloned. For that I ended up using

if (obj.__proto__) {
  copy.__proto__ = Object.assign(Object.create(Object.getPrototypeOf(obj)), obj);
}

PS: Frustrating that I could not find a built in function to do this.


Examples related to javascript

need to add a class to an element How to make a variable accessible outside a function? Hide Signs that Meteor.js was Used How to create a showdown.js markdown extension Please help me convert this script to a simple image slider Highlight Anchor Links when user manually scrolls? Summing radio input values How to execute an action before close metro app WinJS javascript, for loop defines a dynamic variable name Getting all files in directory with ajax

Examples related to ecmascript-6

"Uncaught SyntaxError: Cannot use import statement outside a module" when importing ECMAScript 6 where is create-react-app webpack config and files? Can (a== 1 && a ==2 && a==3) ever evaluate to true? How do I fix "Expected to return a value at the end of arrow function" warning? Enums in Javascript with ES6 Home does not contain an export named Home How to scroll to an element? How to update nested state properties in React eslint: error Parsing error: The keyword 'const' is reserved Node.js ES6 classes with require

Examples related to javascript-objects

Cannot read property 'style' of undefined -- Uncaught Type Error Is this a good way to clone an object in ES6? What’s the difference between “{}” and “[]” while declaring a JavaScript array? Comparing two arrays of objects, and exclude the elements who match values into new array in JS Converting JavaScript object with numeric keys into array From an array of objects, extract value of a property as array How to copy JavaScript object to new variable NOT by reference? Remove property for all objects in array Using curl POST with variables defined in bash script functions How to sum the values of a JavaScript object?