soundly_typed
soundly_typed

Reputation: 40336

How do I correctly clone a JavaScript object?

I have an object x. I'd like to copy it as object y, such that changes to y do not modify x. I realized that copying objects derived from built-in JavaScript objects will result in extra, unwanted properties. This isn't a problem, since I'm copying one of my own literal-constructed objects.

How do I correctly clone a JavaScript object?

Upvotes: 3801

Views: 2482060

Answers (30)

KLASANGUI
KLASANGUI

Reputation: 1146

I saw plenty answers using for ( var i in o ) or Object.keys() to extract an object property. Also, most of the solutions do a shallow clone of an object. Because of this I'll contribute my solution and thoughts here.

IMPORTANT:

  1. for ( var i in o ) just extract the enumerable properties. Ignores all symbol-keys.
  2. Object.keys() just extract string-keys that are enumerable in their property descriptor. Ignores all symbol-keys.
  3. Object.entries() just extract the same as Object.keys() but return the result as a pair like [string-key, value].

The complete solution that I actually do myself is the following:

function PerfectClone ( Subject, IncludePrototypes = false )
{
   // For some odd reason `null` is an object that not strictly
   // resolves into false (it's a falsy though).
   if ( Subject === null ) return null;
   
   // EDIT: this trick will allow to clone even primitives...
   Subject = Object(Subject);
   
   // Creating an empty clean and writable clone.
   const Clone = Object.create(null);
   
   // Filter the only types that can have properties to clone.
   if ( ( typeof Subject == 'function' ) || ( typeof Subject == 'object' ) )
   {
      // Shallow clone the descriptors of `Subject` to `Clone`.
      // Includes `number-keys`, `string-keys` and `symbol-keys`
      // including non-enumerables and getters/setters too.
      Object.defineProperties(Clone, Object.getOwnPropertyDescriptors(Subject));
   }
   
   // We must also perfectly clone the prototype chain...
   let Prototype = Object.getPrototypeOf(Subject);
   
   // Only objects that are not functions need the following...
   if ( typeof Subject == 'object' )
   {
      // We need to extract the object constructor's prototype...
      const ConstructorPrototype = Subject.constructor.prototype;
      
      // Is the constructor's prototype and the subject's the same?
      // Clone the constructor's prototype only if `IncludePrototypes`
      // is truthy.
      if ( IncludePrototypes || ! Object.is(ConstructorPrototype, Prototype) )
      {
         // Replace the prototype to attach with the cloned prototype.
         Prototype = PerfectClone(Prototype, IncludePrototypes);
      }
   }
   
   // When the prototype is properly parsed and evaluated, then
   // we attach it to the clone...
   Object.setPrototypeOf(Clone, Prototype);
   
   // Return the completed clone.
   return Clone;
}

// Example
//////////

class MyStuff
{
   // These are enumerables by default.
   A = 1;
   B = 2;
   
   // This is enumerable, but can't be called through (new MyStuff).C().
   // Can be called if using `((new MyStuff).C)()`.
   C = function Stuff () {};
   
   // This is not enumerable by default.
   D ()
   {
      return this;
   }
   
   // This is not enumerable by default...
   [ Symbol('Anything') ] = 'not enumerable';
   
   // Getters/Setters are actually not enumerable. So...
   // This is not enumerable by default...
   get Name ()
   {
      return MyStuff.name;
   }
   
   // This is not enumerable by default...
   get Type ()
   {
      return this.constructor;
   }
}

class MyExtendedStuff extends MyStuff
{
   // This is not enumerable by default...
   GetMyType ()
   {
      return this.constructor;
   }
   
   // This is not enumerable by default...
   get Name ()
   {
      // This `super.Name` will fail if the prototype chain is
      // not cloned properly.
      return super.Name + '.' + MyExtendedStuff.name;
   }
}

const MyEnhancedStuff = Object.create
(
   new MyExtendedStuff,
   {
      // Symbolic keys are not enumerables by default.
      [ Symbol('Enhanced') ]:
      {
         // So we purposely make it enumerable.
         // Still not shown in default `console.log()` call.
         enumerable: true,
         get: () => MyEnhancedStuff,
      },
      
      // Making an enumerable method...
      MyMethod:
      {
         enumerable: true,
         value: function MyMethod ()
         {
            return this;
         },
      },
   }
);


const MyClone = PerfectClone(MyEnhancedStuff, true);

let Subject = MyClone;

do
{
   // Will not shown hidden keys (symbols, methods, getters/setters
   // and any other not enumerable property)
   console.log(Subject);
   // Node.js debug will show hidden keys perfectly:
   // console.log(( await import('node:util') ).inspect(Subject, true, 3, true));
}
while ( ( Subject = Object.getPrototypeOf(Subject) ) !== null )

NOTE: this will produce a full, complete, clone (not a deep clone) of any kind of object, corretly assigning properties in all the object prototype chain.

If you also clone the "class/function" prototypes, then the output in the console might look creepy, but it's fully functional.

ATENTION: If you use PerfectClone with any "class/function" prototype as the Subject parameter it will be cloned regardless of the IncludePrototypes parameter. If IncludePrototypes parameter is falsy then just the prototype directly at the Subject parameter will be cloned and any other lower prototype will remain in the chain as they are only if they're "class/function" prototype. Else they'll be cloned.

NOTE: It's actually not very interesting to clone "class/function" prototypes because they're globally available in any object from that "class/function". But there's some use cases that need this, so I'm leaving the feature here...

Upvotes: -2

Eugene Tiurin
Eugene Tiurin

Reputation: 4129

An elegant way to clone a Javascript object in one line of code:

An Object.assign method is part of the ECMAScript 2015 (ES6) standard and does exactly what you need.

var clone = Object.assign({}, obj);

The Object.assign() method is used to copy the values of all enumerable own properties from one or more source objects to a target object.

Read more...

The polyfill to support older browsers:

if (!Object.assign) {
  Object.defineProperty(Object, 'assign', {
    enumerable: false,
    configurable: true,
    writable: true,
    value: function(target) {
      'use strict';
      if (target === undefined || target === null) {
        throw new TypeError('Cannot convert first argument to object');
      }

      var to = Object(target);
      for (var i = 1; i < arguments.length; i++) {
        var nextSource = arguments[i];
        if (nextSource === undefined || nextSource === null) {
          continue;
        }
        nextSource = Object(nextSource);

        var keysArray = Object.keys(nextSource);
        for (var nextIndex = 0, len = keysArray.length; nextIndex < len; nextIndex++) {
          var nextKey = keysArray[nextIndex];
          var desc = Object.getOwnPropertyDescriptor(nextSource, nextKey);
          if (desc !== undefined && desc.enumerable) {
            to[nextKey] = nextSource[nextKey];
          }
        }
      }
      return to;
    }
  });
}

Upvotes: 153

Shadman Fatin
Shadman Fatin

Reputation: 170

Simplest way to do is:

let newUser={...user};

Here, 'newUser' is a clone of the 'user' object.

Upvotes: 7

Nor.Z
Nor.Z

Reputation: 1349

To add

  • For comparing the result of
    structuredClone() -- deep copy & Object.assign() -- shallow copy (& Object.keys() -- shallow copy)
    -- to see which level of the object are copied
    here is a demo test case -- with a depth of 4 lv::

  • @code-ex::

        // lv4
        class Material {
          /** @type {String} */ name;
          constructor(name) { this.name = name; } // prettier-ignore
        }
        // lv3
        class Ball {
          /** @type {String} */ name;
          constructor(name) { this.name = name; } // prettier-ignore
          /** @type {Material} */ material;
        }
    
        // lv2
        class NodeX {
          /** @type {String} */ name;
          constructor(name) { this.name = name; } // prettier-ignore
          /** @type {Ball} */ ball;
          /** @type {NodeX} */ node_next;
        }
    
        // lv1
        class ListX {
          /** @type {NodeX} */ node_head;
          /** @type {Number} */ length = 0;
        }
    
        const material_a = new Material('wood');
        const material_b = new Material('stone');
        const ball_a = new Ball('Ball_I');
        const ball_b = new Ball('Ball_II');
        ball_a.material = material_a;
        ball_b.material = material_b;
    
        const node_a = new NodeX('Alpha');
        const node_b = new NodeX('Beta');
        node_a.ball = ball_a;
        node_b.ball = ball_b;
    
        const list = new ListX();
        list.node_head = node_a;
        list.length++;
        node_a.node_next = node_b;
        list.length++;
    
        // #>> structuredClone() -- deep copy
        const list_clone = structuredClone(list); // https://stackoverflow.com/questions/728360/how-do-i-correctly-clone-a-javascript-object
        test_DeepCopy_compare(list_clone, list);
    
        // #>>#
        function test_DeepCopy_compare(list_clone, list) {
          expect(list_clone === list).toEqual(false); // the list is cloned - lv1
          expect(list_clone.length === list.length).toEqual(true);
    
          expect(list_clone.node_head === list.node_head).toEqual(false); // the node (list es property) is also cloned -- deep copy - lv2
    
          const node_a_clone = list_clone.node_head;
          expect(node_a_clone === node_a).toEqual(false); // ~rp
          expect(node_a_clone.name === node_a.name).toEqual(true);
          expect(node_a_clone.node_next === node_a.node_next).toEqual(false); // the node_next (node es property) is also cloned -- deep copy - lv3
    
          const node_b_clone = node_a_clone.node_next;
          expect(node_b_clone === node_b).toEqual(false); // ~rp
          expect(node_b_clone.name === node_b.name).toEqual(true);
          expect(node_b_clone.node_next === undefined && node_b.node_next === undefined).toEqual(true);
    
          expect(node_a_clone.ball === node_a.ball).toEqual(false); // aga, the ball (node es property) is also cloned -- deep copy - lv3
          expect(node_b_clone.ball === node_b.ball).toEqual(false);
    
          const ball_a_clone = node_a_clone.ball;
          const ball_b_clone = node_b_clone.ball;
          expect(ball_a_clone.material === ball_a.material).toEqual(false); // aga, the material (ball es property) is also cloned -- deep copy - lv4
          expect(ball_b_clone.material === ball_b.material).toEqual(false);
        }
    
        // #>> Object.assign() -- shallow copy
        const list_clone_ScOa = Object.create(Object.getPrototypeOf(list)); // const list_clone_ScOa = {}; // https://stackoverflow.com/a/43935938/9549068
        Object.assign(list_clone_ScOa, list); // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/assign#return_value
        test_ShallowCopy_compare(list_clone_ScOa, list); // Object.assign() & Object.keys() produce same test result
    
        // #>> Object.keys() -- shallow copy
        function shallowCopy_ObjectKeys(obj) {
          const obj_clone = Object.create(Object.getPrototypeOf(obj)); // const obj_clone = {};
          for (const propName of Object.keys(obj)) {
            obj_clone[propName] = obj[propName];
          }
          return obj_clone;
        }
    
        const list_clone_ScOk = shallowCopy_ObjectKeys(list);
        test_ShallowCopy_compare(list_clone_ScOk, list);
    
        // #>>#
        function test_ShallowCopy_compare(list_clone_ScOkOa, list) {
          expect(list_clone_ScOkOa === list).toEqual(false); // the list is cloned - lv1
          expect(list_clone_ScOkOa.length === list.length).toEqual(true);
    
          expect(list_clone_ScOkOa.node_head === list.node_head).toEqual(true); // the node (list es property) is NOT cloned -- shallow copy - lv2
    
          const node_a_clone_ScOkOa = list_clone_ScOkOa.node_head;
          expect(node_a_clone_ScOkOa === node_a).toEqual(true); // ~rp
          expect(node_a_clone_ScOkOa.name === node_a.name).toEqual(true);
          expect(node_a_clone_ScOkOa.node_next === node_a.node_next).toEqual(true); // the node_next (node es property) is NOT cloned -- shallow copy - lv3
    
          const node_b_clone_ScOk = node_a_clone_ScOkOa.node_next;
          expect(node_b_clone_ScOk === node_b).toEqual(true); // ~rp
    
          expect(node_a_clone_ScOkOa.ball === node_a.ball).toEqual(true); // aga, the ball (node es property) is NOT cloned -- shallow copy - lv3
          expect(node_b_clone_ScOk.ball === node_b.ball).toEqual(true);
    
          expect(list_clone_ScOkOa instanceof ListX).toEqual(true);
        }
    
  • @comment::

    for Es6 class, seems private / static members are not copied.

    maybe build yourself a own clone() method for your Es6 class is the best way

    For more, see: // https://stackoverflow.com/a/57546221/9549068 // https://stackoverflow.com/a/65024713/9549068

Upvotes: 0

Daevne
Daevne

Reputation: 41

I found a way to clone object with functions (break multiple lines to easier understand):

const clone = Object.assign(
    Object.create(
        Object.getPrototypeOf(originalObject)
    ),
    dataObject
);

Upvotes: 0

A. Levy
A. Levy

Reputation: 30566

2022 update

There's a new JS standard called structured cloning. It works in many browsers (see Can I Use).

const clone = structuredClone(object);

Old answer

To do this for any object in JavaScript will not be simple or straightforward. You will run into the problem of erroneously picking up attributes from the object's prototype that should be left in the prototype and not copied to the new instance. If, for instance, you are adding a clone method to Object.prototype, as some answers depict, you will need to explicitly skip that attribute. But what if there are other additional methods added to Object.prototype, or other intermediate prototypes, that you don't know about? In that case, you will copy attributes you shouldn't, so you need to detect unforeseen, non-local attributes with the hasOwnProperty method.

In addition to non-enumerable attributes, you'll encounter a tougher problem when you try to copy objects that have hidden properties. For example, prototype is a hidden property of a function. Also, an object's prototype is referenced with the attribute __proto__, which is also hidden, and will not be copied by a for/in loop iterating over the source object's attributes. I think __proto__ might be specific to Firefox's JavaScript interpreter and it may be something different in other browsers, but you get the picture. Not everything is enumerable. You can copy a hidden attribute if you know its name, but I don't know of any way to discover it automatically.

Yet another snag in the quest for an elegant solution is the problem of setting up the prototype inheritance correctly. If your source object's prototype is Object, then simply creating a new general object with {} will work, but if the source's prototype is some descendant of Object, then you are going to be missing the additional members from that prototype which you skipped using the hasOwnProperty filter, or which were in the prototype, but weren't enumerable in the first place. One solution might be to call the source object's constructor property to get the initial copy object and then copy over the attributes, but then you still will not get non-enumerable attributes. For example, a Date object stores its data as a hidden member:

function clone(obj) {
    if (null == obj || "object" != typeof obj) return obj;
    var copy = obj.constructor();
    for (var attr in obj) {
        if (obj.hasOwnProperty(attr)) copy[attr] = obj[attr];
    }
    return copy;
}

var d1 = new Date();

/* Executes function after 5 seconds. */
setTimeout(function(){
    var d2 = clone(d1);
    alert("d1 = " + d1.toString() + "\nd2 = " + d2.toString());
}, 5000);

The date string for d1 will be 5 seconds behind that of d2. A way to make one Date the same as another is by calling the setTime method, but that is specific to the Date class. I don't think there is a bullet-proof general solution to this problem, though I would be happy to be wrong!

When I had to implement general deep copying I ended up compromising by assuming that I would only need to copy a plain Object, Array, Date, String, Number, or Boolean. The last 3 types are immutable, so I could perform a shallow copy and not worry about it changing. I further assumed that any elements contained in Object or Array would also be one of the 6 simple types in that list. This can be accomplished with code like the following:

function clone(obj) {
    var copy;

    // Handle the 3 simple types, and null or undefined
    if (null == obj || "object" != typeof obj) return obj;

    // Handle Date
    if (obj instanceof Date) {
        copy = new Date();
        copy.setTime(obj.getTime());
        return copy;
    }

    // Handle Array
    if (obj instanceof Array) {
        copy = [];
        for (var i = 0, len = obj.length; i < len; i++) {
            copy[i] = clone(obj[i]);
        }
        return copy;
    }

    // Handle Object
    if (obj instanceof Object) {
        copy = {};
        for (var attr in obj) {
            if (obj.hasOwnProperty(attr)) copy[attr] = clone(obj[attr]);
        }
        return copy;
    }

    throw new Error("Unable to copy obj! Its type isn't supported.");
}

The above function will work adequately for the 6 simple types I mentioned, as long as the data in the objects and arrays form a tree structure. That is, there isn't more than one reference to the same data in the object. For example:

// This would be cloneable:
var tree = {
    "left"  : { "left" : null, "right" : null, "data" : 3 },
    "right" : null,
    "data"  : 8
};

// This would kind-of work, but you would get 2 copies of the 
// inner node instead of 2 references to the same copy
var directedAcylicGraph = {
    "left"  : { "left" : null, "right" : null, "data" : 3 },
    "data"  : 8
};
directedAcyclicGraph["right"] = directedAcyclicGraph["left"];

// Cloning this would cause a stack overflow due to infinite recursion:
var cyclicGraph = {
    "left"  : { "left" : null, "right" : null, "data" : 3 },
    "data"  : 8
};
cyclicGraph["right"] = cyclicGraph;

It will not be able to handle any JavaScript object, but it may be sufficient for many purposes as long as you don't assume that it will just work for anything you throw at it.

Upvotes: 2047

Pulkit Chaudhri
Pulkit Chaudhri

Reputation: 671

I've had an issue when copying objects. This is because when you do following, you're only making a 'reference' to the object and when the source object value is updated later, the copy object that was cloned also changes value because it was merely a 'reference' and hence you see multiple values of the last changes to the source object.

let x = { a: 1 };
let y = x; // y is a reference to x, so if x changes y also changes and v/v

So, to tackle this issue you do the following:

let y = JSON.parse(JSON.stringify(x)); //see Note below

The other way to prevent references is by doing the following:

let x = { a: 1 };
let y = Object.assign({}, x); // Object.assign(target, ...sources)

y.a = 2;
console.log(x); // { a: 1 }
console.log(y); // { a: 2 }

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

Upvotes: 9

uingtea
uingtea

Reputation: 6524

The different

Only copy top level: {...object} and Object.assign({}, object)

let objA = {
  a: "keyA",
  b: {
    c: "keyC",
  }
}
let objB = Object.assign({}, objA); // or  {...objB}
// change objB
objB.a = "Change objA.a (top)"
console.log("objA.a (top) No Change:\n" + JSON.stringify(objA, false, 2));

objB.b.c = "change should be only for objB.b.c but it in objA.b.c"
console.log("objA.a.c second level has Change:\n" + JSON.stringify(objA, false, 2));

for deep copy use structuredClone() 2022 or JSON.parse(JSON.stringify(object)) for old browser, easy without hack.

let objA = {
  a: "keyA",
  b: {
    c: "keyC",
  }
}
let objB = typeof structuredClone == 'function' ?
  structuredClone(objA) : JSON.parse(JSON.stringify(objA));
// change objB
objB.a = "Change objA.a (top)"
objB.b.c = "change should be only for objB.c but it in objA.c"

console.log("objA has no Change:\n" + JSON.stringify(objA, false, 2));

Upvotes: 2

Jeremy Banks
Jeremy Banks

Reputation: 129715

Structured Cloning

2022 update: The structuredClone() global function is already available in Node 17, Deno 1.14, and most major browsers (see Can I Use).

You can use the same structured clone mechanism that the HTML standard includes for sending data between realms.

const clone = structuredClone(original);

See the other answer for more details.

Upvotes: 21

Ran Turner
Ran Turner

Reputation: 17936

Using the spread syntax performs a shallow copy of the object. This means that none of the nested object instances are cloned as you can see in the following example with the nested object child

const user1 = { 
    name: 'Alex',
    address: '15th Park Avenue',
    age: 43,
    child:{
        name: 'John'
    }
}

const user2 = {...user1};

user1.child.name = 'chris';

console.log(user1);
console.log(user2);

To solve this nested object problem and perform a deep copy we can use JSON.parse(JSON.stringify(someObject))

const user1 = { 
    name: 'Alex',
    address: '15th Park Avenue',
    age: 43,
    child:{
        name: 'John'
    }
}

const user2 = JSON.parse(JSON.stringify(user1));

user1.child.name = 'chris';

console.log(user1);
console.log(user2);

Upvotes: 2

benhatsor
benhatsor

Reputation: 2033

Short and sweet:

let clone = Object.fromEntries(Object.entries(obj));

Demo:

let obj = {a: 'b'};
let clone = Object.fromEntries(Object.entries(obj));

clone.a = 'c';

console.log(obj, clone);

Upvotes: 1

chickens
chickens

Reputation: 22304

Native JS:

const shallowClone = {...originalObj};
const deepClone = JSON.parse(JSON.stringify(originalObj));

Using Libraries:

// Lodash
const shallowClone = _.clone(originalObj);
const deepClone = _. cloneDeep(originalObj);

// JQuery
const shallowClone = jQuery.extend({}, originalObj);
const deepClone = jQuery.extend(true, {}, originalObj);

// Angular
const deepClone = angular.copy(originalObj);

Upvotes: 6

picardo
picardo

Reputation: 24886

function clone(obj) {
    if(obj == null || typeof(obj) != 'object')
        return obj;    
    var temp = new obj.constructor(); 
    for(var key in obj)
        temp[key] = clone(obj[key]);    
    return temp;
}

Upvotes: 27

Mohsen Alyafei
Mohsen Alyafei

Reputation: 5537

Update 06 July 2020

There are three (3) ways to clone objects in JavaScript. As objects in JavaScript are reference values, you can't simply just copy using the =.

The ways are:

const food = { food: 'apple', drink: 'milk' }


// 1. Using the "Spread"
// ------------------

{ ...food }


// 2. Using "Object.assign"
// ------------------

Object.assign({}, food)


// 3. "JSON"
// ------------------

JSON.parse(JSON.stringify(food))

// RESULT:
// { food: 'apple', drink: 'milk' }

This can be used as a reference summary.

Upvotes: 62

Pavan Garre
Pavan Garre

Reputation: 2731

const objClone = { ...obj };

Be aware that nested objects are still copied as a reference.

Upvotes: 41

FAHAD SIDDIQUI
FAHAD SIDDIQUI

Reputation: 641

I have gone through all above solutions and they are quite well. However, there is another approach that you can use to clone object (with values not reference). Object.assign

let x = {
    a: '1',
    b: '2'
}

let y = Object.assign({}, x)
y.a = "3"

console.log(x)

The output will be

{ a: '1', b: '2' }

Moreover, you can also clone array with the same approach.

clonedArray = Object.assign([], array)

Upvotes: 3

Radim Šafr&#225;n
Radim Šafr&#225;n

Reputation: 598

This makes new copy of your obj (not just reference).

let myCopy = JSON.parse(JSON.stringify(obj)); 

..Works much efficiently then the _.cloneDeep(obj).

Upvotes: 1

ooo
ooo

Reputation: 261

(The following was mainly an integration of @Maciej Bukowski, @A. Levy, @Jan Turoň, @Redu's answers, and @LeviRoberts, @RobG's comments, many thanks to them!!!)

Deep copy? — YES! (mostly);
Shallow copy? — NO! (except Proxy).

I sincerely welcome everyone to test clone().
In addition, defineProp() is designed to easily and quickly (re)define or copy any type of descriptor.

Function

function clone(object) {
  /*
    Deep copy objects by value rather than by reference,
    exception: `Proxy`
  */

  const seen = new WeakMap()

  return clone(object)


  function clone(object) {
    if (object !== Object(object)) return object /*
    —— Check if the object belongs to a primitive data type */

    if (object instanceof Node) return object.cloneNode(true) /*
    —— Clone DOM trees */

    let _object // The clone of object

    switch (object.constructor) {
      case Array:
      case Object:
        _object = cloneObject(object)
        break

      case Date:
        _object = new Date(+object)
        break

      case Function:
        _object = copyFn(object)
        break

      case RegExp:
        _object = new RegExp(object)
        break

      default:
        switch (Object.prototype.toString.call(object.constructor)) {
          //                                  // Stem from:
          case "[object Function]":
            switch (object[Symbol.toStringTag]) {
              case undefined:
                _object = cloneObject(object) // `class`
                break

              case "AsyncFunction":
              case "GeneratorFunction":
              case "AsyncGeneratorFunction":
                _object = copyFn(object)
                break

              default:
                _object = object
            }
            break

          case "[object Undefined]":          // `Object.create(null)`
            _object = cloneObject(object)
            break

          default:
            _object = object                  // `Proxy`
        }
    }

    return _object
  }


  function cloneObject(object) {
    if (seen.has(object)) return seen.get(object) /*
    —— Handle recursive references (circular structures) */

    const _object = Array.isArray(object)
      ? []
      : Object.create(Object.getPrototypeOf(object)) /*
        —— Assign [[Prototype]] for inheritance */

    seen.set(object, _object) /*
    —— Make `_object` the associative mirror of `object` */

    Reflect.ownKeys(object).forEach(key =>
      defineProp(_object, key, { value: clone(object[key]) }, object)
    )

    return _object
  }
}


function copyPropDescs(target, source) {
  Object.defineProperties(target,
    Object.getOwnPropertyDescriptors(source)
  )
}


function convertFnToStr(fn) {
  let fnStr = String(fn)
  if (fn.name.startsWith("[")) // isSymbolKey
    fnStr = fnStr.replace(/\[Symbol\..+?\]/, '')
  fnStr = /^(?!(async )?(function\b|[^{]+?=>))[^(]+?\(/.test(fnStr)
    ? fnStr.replace(/^(async )?(\*)?/, "$1function$2 ") : fnStr
  return fnStr
}

function copyFn(fn) {
  const newFn = new Function(`return ${convertFnToStr(fn)}`)()
  copyPropDescs(newFn, fn)
  return newFn
}



function defineProp(object, key, descriptor = {}, copyFrom = {}) {
  const { configurable: _configurable, writable: _writable }
    = Object.getOwnPropertyDescriptor(object, key)
    || { configurable: true, writable: true }

  const test = _configurable // Can redefine property
    && (_writable === undefined || _writable) // Can assign to property

  if (!test || arguments.length <= 2) return test

  const basisDesc = Object.getOwnPropertyDescriptor(copyFrom, key)
    || { configurable: true, writable: true } // Custom…
    || {}; // …or left to native default settings

  ["get", "set", "value", "writable", "enumerable", "configurable"]
    .forEach(attr =>
      descriptor[attr] === undefined &&
      (descriptor[attr] = basisDesc[attr])
    )

  const { get, set, value, writable, enumerable, configurable }
    = descriptor

  return Object.defineProperty(object, key, {
    enumerable, configurable, ...get || set
      ? { get, set } // Accessor descriptor
      : { value, writable } // Data descriptor
  })
}

// Tests

const obj0 = {
  u: undefined,
  nul: null,
  t: true,
  num: 9,
  str: "",
  sym: Symbol("symbol"),
  [Symbol("e")]: Math.E,
  arr: [[0], [1, 2]],
  d: new Date(),
  re: /f/g,
  get g() { return 0 },
  o: {
    n: 0,
    o: { f: function (...args) { } }
  },
  f: {
    getAccessorStr(object) {
      return []
        .concat(...
          Object.values(Object.getOwnPropertyDescriptors(object))
            .filter(desc => desc.writable === undefined)
            .map(desc => Object.values(desc))
        )
        .filter(prop => typeof prop === "function")
        .map(String)
    },
    f0: function f0() { },
    f1: function () { },
    f2: a => a / (a + 1),
    f3: () => 0,
    f4(params) { return param => param + params },
    f5: (a, b) => ({ c = 0 } = {}) => a + b + c
  }
}

defineProp(obj0, "s", { set(v) { this._s = v } })
defineProp(obj0.arr, "tint", { value: { is: "non-enumerable" } })
obj0.arr[0].name = "nested array"


let obj1 = clone(obj0)
obj1.o.n = 1
obj1.o.o.g = function g(a = 0, b = 0) { return a + b }
obj1.arr[1][1] = 3
obj1.d.setTime(+obj0.d + 60 * 1000)
obj1.arr.tint.is = "enumerable? no"
obj1.arr[0].name = "a nested arr"
defineProp(obj1, "s", { set(v) { this._s = v + 1 } })
defineProp(obj1.re, "multiline", { value: true })

console.log("\n\n" + "-".repeat(2 ** 6))




console.log(">:>: Test - Routinely")

console.log("obj0:\n ", JSON.stringify(obj0))
console.log("obj1:\n ", JSON.stringify(obj1))
console.log()

console.log("obj0:\n ", obj0)
console.log("obj1:\n ", obj1)
console.log()

console.log("obj0\n ",
  ".arr.tint:", obj0.arr.tint, "\n ",
  ".arr[0].name:", obj0.arr[0].name
)
console.log("obj1\n ",
  ".arr.tint:", obj1.arr.tint, "\n ",
  ".arr[0].name:", obj1.arr[0].name
)
console.log()

console.log("Accessor-type descriptor\n ",
  "of obj0:", obj0.f.getAccessorStr(obj0), "\n ",
  "of obj1:", obj1.f.getAccessorStr(obj1), "\n ",
  "set (obj0 & obj1) .s :", obj0.s = obj1.s = 0, "\n ",
  "  → (obj0 , obj1) ._s:", obj0._s, ",", obj1._s
)

console.log("—— obj0 has not been interfered.")

console.log("\n\n" + "-".repeat(2 ** 6))




console.log(">:>: Test - More kinds of functions")

const fnsForTest = {
  f(_) { return _ },
  func: _ => _,
  aFunc: async _ => _,
  async function() { },
  async asyncFunc() { },
  aFn: async function () { },
  *gen() { },
  async *asyncGen() { },
  aG1: async function* () { },
  aG2: async function* gen() { },
  *[Symbol.iterator]() { yield* Object.keys(this) }
}

console.log(Reflect.ownKeys(fnsForTest).map(k =>
  `${String(k)}:
  ${fnsForTest[k].name}-->
    ${String(fnsForTest[k])}`
).join("\n"))

const normedFnsStr = `{
  f: function f(_) { return _ },
  func: _ => _,
  aFunc: async _ => _,
  function: async function() { },
  asyncFunc: async function asyncFunc() { },
  aFn: async function () { },
  gen: function* gen() { },
  asyncGen: async function* asyncGen() { },
  aG1: async function* () { },
  aG2: async function* gen() { },
  [Symbol.iterator]: function* () { yield* Object.keys(this) }
}`

const copiedFnsForTest = clone(fnsForTest)
console.log("fnsForTest:", fnsForTest)
console.log("fnsForTest (copied):", copiedFnsForTest)
console.log("fnsForTest (normed str):", eval(`(${normedFnsStr})`))
console.log("Comparison of fnsForTest and its clone:",
  Reflect.ownKeys(fnsForTest).map(k =>
    [k, fnsForTest[k] === copiedFnsForTest[k]]
  )
)

console.log("\n\n" + "-".repeat(2 ** 6))




console.log(">:>: Test - Circular structures")

obj0.o.r = {}
obj0.o.r.recursion = obj0.o
obj0.arr[1] = obj0.arr

obj1 = clone(obj0)
console.log("obj0:\n ", obj0)
console.log("obj1:\n ", obj1)

console.log("Clear obj0's recursion:",
  obj0.o.r.recursion = null, obj0.arr[1] = 1
)
console.log(
  "obj0\n ",
  ".o.r:", obj0.o.r, "\n ",
  ".arr:", obj0.arr
)
console.log(
  "obj1\n ",
  ".o.r:", obj1.o.r, "\n ",
  ".arr:", obj1.arr
)
console.log("—— obj1 has not been interfered.")


console.log("\n\n" + "-".repeat(2 ** 6))




console.log(">:>: Test - Classes")

class Person {
  constructor(name) {
    this.name = name
  }
}

class Boy extends Person { }
Boy.prototype.sex = "M"

const boy0 = new Boy
boy0.hobby = { sport: "spaceflight" }

const boy1 = clone(boy0)
boy1.hobby.sport = "superluminal flight"

boy0.name = "one"
boy1.name = "neo"

console.log("boy0:\n ", boy0)
console.log("boy1:\n ", boy1)
console.log("boy1's prototype === boy0's:",
  Object.getPrototypeOf(boy1) === Object.getPrototypeOf(boy0)
)

References

  1. Object.create() | MDN
  2. Object.defineProperties() | MDN
  3. Enumerability and ownership of properties | MDN
  4. TypeError: cyclic object value | MDN

Language tricks used

  1. Conditionally add prop to object

Upvotes: 11

Vivek sharma
Vivek sharma

Reputation: 53

You can use rest operator to clone arrays or objects

let myObj = {1: 100, 'a': 200};

let clone = {...myObj}; 

clone.a = 300;

console.log(clone.a) // Output :- 300
console.log(myObj.a) // Output :- 200

Upvotes: 1

Dere Sagar
Dere Sagar

Reputation: 1798

Ways to Copy Objects in JavaScript

  1. Use the spread (...) syntax
  2. Use the Object.assign() method
  3. Use the JSON.stringify() and JSON.parse() methods
const person = {
    firstName: 'John',
    lastName: 'Doe'
};

// using spread ...
let p1 = {
    ...person
};

// using  Object.assign() method
let p2 = Object.assign({}, person);

// using JSON
let p3 = JSON.parse(JSON.stringify(person));

Upvotes: 2

rela589n
rela589n

Reputation: 1086

The most correct to copy object is use Object.create:

Object.create(Object.getPrototypeOf(obj), Object.getOwnPropertyDescriptors(obj));

Such notation will make identically the same object with correct prototype and hidden properties.

Upvotes: 10

Vitalii Fedorenko
Vitalii Fedorenko

Reputation: 114400

In ECMAScript 6 there is Object.assign method, which copies values of all enumerable own properties from one object to another. For example:

var x = {myProp: "value"};
var y = Object.assign({}, x); 

But be aware this is a shallow copy - nested objects are still copied as reference.

Upvotes: 850

Kamil Kiełczewski
Kamil Kiełczewski

Reputation: 92347

Performance

Today 2020.04.30 I perform tests of chosen solutions on Chrome v81.0, Safari v13.1 and Firefox v75.0 on MacOs High Sierra v10.13.6.

I focus on speed of copy DATA (object with simple type fields, not methods etc.). The solutions A-I can make only shallow copy, solutions J-U can make deep copy.

Results for shallow copy

  • solution {...obj} (A) is fastest on chrome and firefox and medium fast on safari
  • solution based on Object.assign (B) is fast on all browsers
  • jQuery (E) and lodash (F,G,H) solutions are medium/quite fast
  • solution JSON.parse/stringify (K) is quite slow
  • solutions D and U are slow on all browsers

enter image description here

Results for deep copy

  • solution Q is fastest on all browsers
  • jQuery (L) and lodash (J) are medium fast
  • solution JSON.parse/stringify (K) is quite slow
  • solution U is slowest on all browsers
  • lodash (J) and solution U crash on Chrome for 1000 level deep object

enter image description here

Details

For choosen solutions: A B C(my) D E F G H I J K L M N O P Q R S T U, I perform 4 tests

  • shallow-small: object with 10 non-nested fields - you can run it HERE
  • shallow-big: object with 1000 non-nested fields - you can run it HERE
  • deep-small: object with 10 levels-nested fields - you can run it HERE
  • deep-big: object with 1000 levels-nested fields - you can run it HERE

Objects used in tests are show in below snippet

let obj_ShallowSmall = {
  field0: false,
  field1: true,
  field2: 1,
  field3: 0,
  field4: null,
  field5: [],
  field6: {},
  field7: "text7",
  field8: "text8",
}

let obj_DeepSmall = {
  level0: {
   level1: {
    level2: {
     level3: {
      level4: {
       level5: {
        level6: {
         level7: {
          level8: {
           level9: [[[[[[[[[['abc']]]]]]]]]],
  }}}}}}}}},
};

let obj_ShallowBig = Array(1000).fill(0).reduce((a,c,i) => (a['field'+i]=getField(i),a) ,{});


let obj_DeepBig = genDeepObject(1000);



// ------------------
// Show objects
// ------------------

console.log('obj_ShallowSmall:',JSON.stringify(obj_ShallowSmall));
console.log('obj_DeepSmall:',JSON.stringify(obj_DeepSmall));
console.log('obj_ShallowBig:',JSON.stringify(obj_ShallowBig));
console.log('obj_DeepBig:',JSON.stringify(obj_DeepBig));




// ------------------
// HELPERS
// ------------------

function getField(k) {
  let i=k%10;
  if(i==0) return false;
  if(i==1) return true;
  if(i==2) return k;
  if(i==3) return 0;
  if(i==4) return null;
  if(i==5) return [];
  if(i==6) return {};  
  if(i>=7) return "text"+k;
}

function genDeepObject(N) {
  // generate: {level0:{level1:{...levelN: {end:[[[...N-times...['abc']...]]] }}}...}}}
  let obj={};
  let o=obj;
  let arr = [];
  let a=arr;

  for(let i=0; i<N; i++) {
    o['level'+i]={};
    o=o['level'+i];
    let aa=[];
    a.push(aa);
    a=aa;
  }

  a[0]='abc';
  o['end']=arr;
  return obj;
}

Below snippet presents tested solutions and shows differences between them

function A(obj) {
  return {...obj}
}

function B(obj) {
  return Object.assign({}, obj); 
}

function C(obj) {
  return Object.keys(obj).reduce( (a,c) => (a[c]=obj[c], a), {})
}

function D(obj) {
  let copyOfObject = {};
  Object.defineProperties(copyOfObject, Object.getOwnPropertyDescriptors(obj));
  return copyOfObject;
}

function E(obj) {
  return jQuery.extend({}, obj) // shallow
}

function F(obj) {
  return _.clone(obj);
}

function G(obj) {
  return _.clone(obj,true);
}

function H(obj) {
  return _.extend({},obj);
}

function I(obj) {
    if (null == obj || "object" != typeof obj) return obj;
    var copy = obj.constructor();
    for (var attr in obj) {
        if (obj.hasOwnProperty(attr)) copy[attr] = obj[attr];
    }
    return copy;
}

function J(obj) {
  return _.cloneDeep(obj,true);
}

function K(obj) {
	return JSON.parse(JSON.stringify(obj));
}

function L(obj) {
  return jQuery.extend(true, {}, obj) // deep
}

function M(obj) {
  if(obj == null || typeof(obj) != 'object')
    return obj;    
  var temp = new obj.constructor(); 
  for(var key in obj)
    temp[key] = M(obj[key]);    
  return temp;
}

function N(obj) {
  let EClone = function(obj) {
    var newObj = (obj instanceof Array) ? [] : {};
    for (var i in obj) {
      if (i == 'EClone') continue;
      if (obj[i] && typeof obj[i] == "object") {
        newObj[i] = EClone(obj[i]);
      } else newObj[i] = obj[i]
    } return newObj;
  };

	return EClone(obj);
};

function O(obj) {
    if (obj == null || typeof obj != "object") return obj;
    if (obj.constructor != Object && obj.constructor != Array) return obj;
    if (obj.constructor == Date || obj.constructor == RegExp || obj.constructor == Function ||
        obj.constructor == String || obj.constructor == Number || obj.constructor == Boolean)
        return new obj.constructor(obj);

    let to = new obj.constructor();

    for (var name in obj)
    {
        to[name] = typeof to[name] == "undefined" ? O(obj[name], null) : to[name];
    }

    return to;
}

function P(obj) {
  function clone(target, source){

      for(let key in source){

          // Use getOwnPropertyDescriptor instead of source[key] to prevent from trigering setter/getter.
          let descriptor = Object.getOwnPropertyDescriptor(source, key);
          if(descriptor.value instanceof String){
              target[key] = new String(descriptor.value);
          }
          else if(descriptor.value instanceof Array){
              target[key] = clone([], descriptor.value);
          }
          else if(descriptor.value instanceof Object){
              let prototype = Reflect.getPrototypeOf(descriptor.value);
              let cloneObject = clone({}, descriptor.value);
              Reflect.setPrototypeOf(cloneObject, prototype);
              target[key] = cloneObject;
          }
          else {
              Object.defineProperty(target, key, descriptor);
          }
      }
      let prototype = Reflect.getPrototypeOf(source);
      Reflect.setPrototypeOf(target, prototype);
      return target;
  }
  return clone({},obj);
}

function Q(obj) {
    var copy;

    // Handle the 3 simple types, and null or undefined
    if (null == obj || "object" != typeof obj) return obj;

    // Handle Date
    if (obj instanceof Date) {
        copy = new Date();
        copy.setTime(obj.getTime());
        return copy;
    }

    // Handle Array
    if (obj instanceof Array) {
        copy = [];
        for (var i = 0, len = obj.length; i < len; i++) {
            copy[i] = Q(obj[i]);
        }
        return copy;
    }

    // Handle Object
    if (obj instanceof Object) {
        copy = {};
        for (var attr in obj) {
            if (obj.hasOwnProperty(attr)) copy[attr] = Q(obj[attr]);
        }
        return copy;
    }

    throw new Error("Unable to copy obj! Its type isn't supported.");
}

function R(obj) {
    const gdcc = "__getDeepCircularCopy__";
    if (obj !== Object(obj)) {
        return obj; // primitive value
    }

    var set = gdcc in obj,
        cache = obj[gdcc],
        result;
    if (set && typeof cache == "function") {
        return cache();
    }
    // else
    obj[gdcc] = function() { return result; }; // overwrite
    if (obj instanceof Array) {
        result = [];
        for (var i=0; i<obj.length; i++) {
            result[i] = R(obj[i]);
        }
    } else {
        result = {};
        for (var prop in obj)
            if (prop != gdcc)
                result[prop] = R(obj[prop]);
            else if (set)
                result[prop] = R(cache);
    }
    if (set) {
        obj[gdcc] = cache; // reset
    } else {
        delete obj[gdcc]; // unset again
    }
    return result;
}

function S(obj) {
    const cache = new WeakMap(); // Map of old - new references

    function copy(object) {
        if (typeof object !== 'object' ||
            object === null ||
            object instanceof HTMLElement
        )
            return object; // primitive value or HTMLElement

        if (object instanceof Date) 
            return new Date().setTime(object.getTime());

        if (object instanceof RegExp) 
            return new RegExp(object.source, object.flags);

        if (cache.has(object)) 
            return cache.get(object);

        const result = object instanceof Array ? [] : {};

        cache.set(object, result); // store reference to object before the recursive starts

        if (object instanceof Array) {
            for(const o of object) {
                 result.push(copy(o));
            }
            return result;
        }

        const keys = Object.keys(object); 

        for (const key of keys)
            result[key] = copy(object[key]);

        return result;
    }

    return copy(obj);
}

function T(obj){
    var clonedObjectsArray = [];
    var originalObjectsArray = []; //used to remove the unique ids when finished
    var next_objid = 0;

    function objectId(obj) {
        if (obj == null) return null;
        if (obj.__obj_id == undefined){
            obj.__obj_id = next_objid++;
            originalObjectsArray[obj.__obj_id] = obj;
        }
        return obj.__obj_id;
    }

    function cloneRecursive(obj) {
        if (null == obj || typeof obj == "string" || typeof obj == "number" || typeof obj == "boolean") return obj;

        // Handle Date
        if (obj instanceof Date) {
            var copy = new Date();
            copy.setTime(obj.getTime());
            return copy;
        }

        // Handle Array
        if (obj instanceof Array) {
            var copy = [];
            for (var i = 0; i < obj.length; ++i) {
                copy[i] = cloneRecursive(obj[i]);
            }
            return copy;
        }

        // Handle Object
        if (obj instanceof Object) {
            if (clonedObjectsArray[objectId(obj)] != undefined)
                return clonedObjectsArray[objectId(obj)];

            var copy;
            if (obj instanceof Function)//Handle Function
                copy = function(){return obj.apply(this, arguments);};
            else
                copy = {};

            clonedObjectsArray[objectId(obj)] = copy;

            for (var attr in obj)
                if (attr != "__obj_id" && obj.hasOwnProperty(attr))
                    copy[attr] = cloneRecursive(obj[attr]);                 

            return copy;
        }       


        throw new Error("Unable to copy obj! Its type isn't supported.");
    }
    var cloneObj = cloneRecursive(obj);



    //remove the unique ids
    for (var i = 0; i < originalObjectsArray.length; i++)
    {
        delete originalObjectsArray[i].__obj_id;
    };

    return cloneObj;
}

function U(obj) {
  /*
    Deep copy objects by value rather than by reference,
    exception: `Proxy`
  */

  const seen = new WeakMap()

  return clone(obj)

  function defineProp(object, key, descriptor = {}, copyFrom = {}) {
    const { configurable: _configurable, writable: _writable }
      = Object.getOwnPropertyDescriptor(object, key)
      || { configurable: true, writable: true }

    const test = _configurable // Can redefine property
      && (_writable === undefined || _writable) // Can assign to property

    if (!test || arguments.length <= 2) return test

    const basisDesc = Object.getOwnPropertyDescriptor(copyFrom, key)
      || { configurable: true, writable: true } // Custom…
      || {}; // …or left to native default settings

    ["get", "set", "value", "writable", "enumerable", "configurable"]
      .forEach(attr =>
        descriptor[attr] === undefined &&
        (descriptor[attr] = basisDesc[attr])
      )

    const { get, set, value, writable, enumerable, configurable }
      = descriptor

    return Object.defineProperty(object, key, {
      enumerable, configurable, ...get || set
        ? { get, set } // Accessor descriptor
        : { value, writable } // Data descriptor
    })
  }

  function clone(object) {
    if (object !== Object(object)) return object /*
    —— Check if the object belongs to a primitive data type */

    if (object instanceof Node) return object.cloneNode(true) /*
    —— Clone DOM trees */

    let _object // The clone of object

    switch (object.constructor) {
      case Array:
      case Object:
        _object = cloneObject(object)
        break

      case Date:
        _object = new Date(+object)
        break

      case Function:
        const fnStr = String(object)

        _object = new Function("return " +
          (/^(?!function |[^{]+?=>)[^(]+?\(/.test(fnStr)
            ? "function " : ""
          ) + fnStr
        )()

        copyPropDescs(_object, object)
        break

      case RegExp:
        _object = new RegExp(object)
        break

      default:
        switch (Object.prototype.toString.call(object.constructor)) {
          //                              // Stem from:
          case "[object Function]":       // `class`
          case "[object Undefined]":      // `Object.create(null)`
            _object = cloneObject(object)
            break

          default:                        // `Proxy`
            _object = object
        }
    }

    return _object
  }


  function cloneObject(object) {
    if (seen.has(object)) return seen.get(object) /*
    —— Handle recursive references (circular structures) */

    const _object = Array.isArray(object)
      ? []
      : Object.create(Object.getPrototypeOf(object)) /*
        —— Assign [[Prototype]] for inheritance */

    seen.set(object, _object) /*
    —— Make `_object` the associative mirror of `object` */

    Reflect.ownKeys(object).forEach(key =>
      defineProp(_object, key, { value: clone(object[key]) }, object)
    )

    return _object
  }


  function copyPropDescs(target, source) {
    Object.defineProperties(target,
      Object.getOwnPropertyDescriptors(source)
    )
  }
}
 
// ------------------------
// Test properties
// ------------------------


console.log(`  shallow deep  func  circ  undefined date  RegExp bigInt`)

log(A);
log(B);
log(C);
log(D);
log(E);
log(F);
log(G);
log(H);
log(I);
log(J);
log(K);
log(L);
log(M);
log(N);
log(O);
log(P);
log(Q);
log(R);
log(S);
log(T);
log(U);

console.log(`  shallow deep  func  circ  undefined date  RegExp bigInt
----
LEGEND:
shallow - solution create shallow copy
deep - solution create deep copy
func - solution copy functions
circ - solution can copy object with circular references
undefined - solution copy fields with undefined value
date - solution can copy date
RegExp - solution can copy fields with regular expressions
bigInt - solution can copy BigInt
`)


// ------------------------
// Helper functions
// ------------------------


function deepCompare(obj1,obj2) {
  return JSON.stringify(obj1)===JSON.stringify(obj2);
}

function getCase() { // pure data case
  return { 
    undef: undefined,
    bool: true, num: 1, str: "txt1",    
    e1: null, e2: [], e3: {}, e4: 0, e5: false,
    arr: [ false, 2, "txt3", null, [], {},
      [ true,4,"txt5",null, [], {},  [true,6,"txt7",null,[],{} ], 
        {bool: true,num: 8, str: "txt9", e1:null, e2:[] ,e3:{} ,e4: 0, e5: false}
      ],
        {bool: true,num: 10, str: "txt11", e1:null, e2:[] ,e3:{} ,e4: 0, e5: false}
    ], 
    obj: { 
        bool: true, num: 12, str: "txt13",
        e1: null, e2: [], e3: {}, e4: 0, e5: false,
        arr: [true,14,"txt15",null,[],{} ],
        obj: { 
          bool: true, num: 16, str: "txt17",
          e1: null, e2: [], e3: {}, e4: 0, e5: false,
          arr: [true,18,"txt19",null,[],{} ],
          obj: {bool: true,num: 20, str: "txt21", e1:null, e2:[] ,e3:{} ,e4: 0, e5: false}
      } 
    } 
  };
}

function check(org, copy, field, newValue) {
  copy[field] = newValue;
  return deepCompare(org,copy); 
}

function testFunc(f) {
	let o = { a:1, fun: (i,j)=> i+j };
  let c = f(o);
  
  let val = false
  try{
    val = c.fun(3,4)==7;
  } catch(e) { }
  return val;
} 

function testCirc(f) {
	function Circ() {
    this.me = this;
  }

  var o = {
      x: 'a',
      circ: new Circ(),
      obj_circ: null,
  };
  
  o.obj_circ = o;

  let val = false;

  try{
    let c = f(o);  
    val = (o.obj_circ == o) && (o.circ == o.circ.me);
  } catch(e) { }
  return val;
} 

function testRegExp(f) {
  let o = {
    re: /a[0-9]+/,
  };
  
  let val = false;

  try{
    let c = f(o);  
    val = (String(c.re) == String(/a[0-9]+/));
  } catch(e) { }
  return val;
}

function testDate(f) {
  let o = {
    date: new Date(),
  };
  
  let val = false;

  try{
    let c = f(o);  
    val = (+new Date(c.date) == +new Date(o.date));
  } catch(e) { }
  return val;
}

function testBigInt(f) {
  let val = false;
  
  try{
    let o = {
      big: 123n,
    };
  
    let c = f(o);  
  
    val = o.big == c.big;
  } catch(e) { }
  
  return val;
}

function log(f) {
  let o = getCase();  // orginal object
  let oB = getCase(); // "backup" used for shallow valid test
  
  let c1 = f(o); // copy 1 for reference
  let c2 = f(o); // copy 2 for test shallow values
  let c3 = f(o); // copy 3 for test deep values

  let is_proper_copy = deepCompare(c1,o);  // shoud be true
  
  // shallow changes
  let testShallow = 
    [ ['bool',false],['num',666],['str','xyz'],['arr',[]],['obj',{}] ]
    .reduce((acc,curr)=> acc && check(c1,c2,curr[0], curr[1]), true );
  
  // should be true (original object shoud not have changed shallow fields)
  let is_valid = deepCompare(o,oB); 

  // deep test (intruduce some change)
  if (c3.arr[6]) c3.arr[6][7].num = 777;
  
  let diff_shallow = !testShallow; // shoud be true (shallow field was copied)
  let diff_deep = !deepCompare(c1,c3);    // shoud be true (deep field was copied)
  let can_copy_functions = testFunc(f);
  let can_copy_circular = testCirc(f);
  let can_copy_regexp = testRegExp(f);
  let can_copy_date = testDate(f);
  let can_copy_bigInt = testBigInt(f);
  
  let has_undefined = 'undef' in c1; // field with undefined value is copied?  
  let is_ok = is_valid && is_proper_copy;
  let b=(bool) => (bool+'').padEnd(5,' '); // bool value to formated string
  
  testFunc(f);
  
  if(is_ok) {
    console.log(`${f.name} ${b(diff_shallow)}   ${b(diff_deep)} ${b(can_copy_functions)} ${b(can_copy_circular)} ${b(has_undefined)}     ${b(can_copy_date)} ${b(can_copy_regexp)}  ${b(can_copy_bigInt)}`)
  } else {
    console.log(`${f.name}: INVALID ${is_valid} ${is_proper_copy}`,{c1})
  }
  
}
<script src="https://code.jquery.com/jquery-3.5.0.min.js" integrity="sha256-xNzN2a4ltkB44Mc/Jz3pT4iU1cmeR0FkXs4pru/JxaQ=" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/lodash.min.js"></script>

This snippet only presents tested solutions and show differences between them (but it no make performence tests)

Below there are example results for Chrome for shallow-big object

enter image description here

Upvotes: 27

Pouria Moosavi
Pouria Moosavi

Reputation: 730

Just as this link says use this code:

let clone = Object.create(Object.getPrototypeOf(obj),
 Object.getOwnPropertyDescriptors(obj));

Upvotes: 4

Calvin
Calvin

Reputation: 4619

From this article: How to copy arrays and objects in Javascript by Brian Huisman:

Object.prototype.clone = function() {
  var newObj = (this instanceof Array) ? [] : {};
  for (var i in this) {
    if (i == 'clone') continue;
    if (this[i] && typeof this[i] == "object") {
      newObj[i] = this[i].clone();
    } else newObj[i] = this[i]
  } return newObj;
};

Upvotes: 30

asoni94
asoni94

Reputation: 147

The solution JSON.parse(JSON.stringify(orig_obj) as stated by many peers here for deep_cloning has several issues which I found, and they are listed below:

  1. It discards the entries while copying whose values are undefined in the original object,
  2. If there are some values like Infinity, NaN etc, they will be converted into null while copying,
  3. If there is a Date type in the original object, it will be stringified in the cloned object (typeof date_entry --> string).

Found an effective way for cloning an object, and it worked well for me in all sort of scenarios. Please have a look at below code, as it has resolved all above mentioned pitfalls of JSON.parse(...), yet resulting in proper deep-cloning:

var orig_obj = {
  string: 'my_str',
  number: 123,
  bool: false,
  nul: null,
  nested : {
    value : true
  },
  nan : NaN,
  date: new Date(), 
  undef: undefined,
  inf: Infinity,
}
console.log("original_obj before modification: ", orig_obj, "\n");
console.log(typeof orig_obj.date, "\n");

var clone_obj = Object.assign({}, orig_obj);

//this below loop will help in deep cloning and solving above issues
for(let prop in orig_obj) {
    if(typeof orig_obj[prop] === "object") {
        if(orig_obj[prop] instanceof Date)
            clone_obj[prop] = orig_obj[prop];
        else {
            clone_obj[prop] = JSON.parse(JSON.stringify(orig_obj[prop]));
        }
    }
}

console.log("cloned_obj before modification: ", orig_obj, "\n");

clone_obj.bool = true;
clone_obj.nested.value = "false";

console.log("original_obj post modification: ", orig_obj, "\n");
console.log("cloned_obj post modification: ", clone_obj, "\n");
console.log(typeof clone_obj.date);

Upvotes: 3

Raskolnikov
Raskolnikov

Reputation: 159

Simple recursive method to clone an object. Also could use lodash.clone.

let clone = (obj) => {
	let obj2 = Array.isArray(obj) ? [] : {};
	for(let k in obj) {
          obj2[k] = (typeof obj[k] === 'object' ) ? clone(obj[k]) :  obj[k];
        }
        return obj2;
    }

let w = { name: "Apple", types: ["Fuji", "Gala"]};
let x = clone(w);
w.name = "Orange";
w.types = ["Navel"];
console.log(x);
console.log(w);

Upvotes: 5

heinob
heinob

Reputation: 19464

If you do not use Dates, functions, undefined, regExp or Infinity within your object, a very simple one liner is JSON.parse(JSON.stringify(object)):

const a = {
  string: 'string',
  number: 123,
  bool: false,
  nul: null,
  date: new Date(),  // stringified
  undef: undefined,  // lost
  inf: Infinity,  // forced to 'null'
}
console.log(a);
console.log(typeof a.date);  // Date object
const clone = JSON.parse(JSON.stringify(a));
console.log(clone);
console.log(typeof clone.date);  // result of .toISOString()

This works for all kind of objects containing objects, arrays, strings, booleans and numbers.

See also this article about the structured clone algorithm of browsers which is used when posting messages to and from a worker. It also contains a function for deep cloning.

Upvotes: 1174

Fouad Boukredine
Fouad Boukredine

Reputation: 1623

var x = {'e': 2, 'd': 8, 'b': 5};

const y = {};
for(let key in x) {
    y[key] = x[key];
}
console.log(y); // =>>> {e: 2, d: 8, b: 5}

const z = {};
Object.keys(x).forEach(key => {
    z[key] = x[key];
});
console.log(z); // =>>> {e: 2, d: 8, b: 5}

const w = {};
for(let i = 0; i < Object.keys(x).length; i++) {
    w[Object.keys(x)[i]] = x[Object.keys(x)[i]];
}
console.log(w); // =>>> {e: 2, d: 8, b: 5}

const v = {};
for(let key of Object.keys(x)) {
    v[key] = x[key];
}
console.log(v); // =>>> {e: 2, d: 8, b: 5}

x['q'] = 100;   // Altering x will not affect the other objects

console.log(x); // =>>> {e: 2, d: 8, b: 5, q: 100}
console.log(y); // =>>> {e: 2, d: 8, b: 5}
console.log(z); // =>>> {e: 2, d: 8, b: 5}
console.log(w); // =>>> {e: 2, d: 8, b: 5}
console.log(v); // =>>> {e: 2, d: 8, b: 5}

Upvotes: 1

GANESH CHOKHARE
GANESH CHOKHARE

Reputation: 355

Object copy using ( ... )

//bad
const original = { a: 1, b: 2 };
const copy = Object.assign({}, original, { c: 3 }); // copy => { a: 1, b: 2,c: 3 }

//good
const originalObj = { id: 5, name: 'San Francisco'};
const copyObject = {...originalObj, pincode: 4444};
console.log(copyObject)  //{ id: 5, name: 'San Francisco', pincode: 4444 }

Same can be use for copying array from one to other

const itemsCopy = [...items];

Upvotes: 5

Related Questions