Clone an object in vanilla JS - multiple ways

author-avatar
Paul Shan Wed Mar 08 2017

Objects are passed by reference, but sometimes you don’t want that. Thus a need of cloning or copying objects.
Though the work looks very simple, but actually it is NOT. However depending on your requirement it can be very simple too. I will show you different ways to copy or clone objects from easy to complex.

JSON.stringify is the easiest

//clone with JSON.stringify
var oldObj = {
        prop1: "I am value",
        prop2: {
            some: "thing"
        }
}

var clonnedObj = JSON.parse(JSON.stringify(oldObj))

Pros:
* Easy to implement.
* Short and simple code.
* Great if you just want to copy a simple plain object.

Cons:
* High CPU work.
* Prototype will be lost. Cloned object will be created from Object class.
* Will throw error in circular objects.

//Will fail here
var someObj = {
        prop1: {
            some: "thing"
        }
}
var someOtherObj = {
    propX: someObj
}
someObj.someProp = someOtherObj;

var clonnedObj = JSON.parse(JSON.stringify(someObj))
//ERROR

Object.assign({}, obj)

//clone with Object.assign()
var oldObj = {
        prop1: "I am value",
        prop2: {
            some: "thing"
        }
}

var clonnedObj = Object.assign({}, oldObj);
//This will copy the properties, but if any property that holds an object value, will be passed by reference.

Pros:
* Easy to use. Short and simple.
* Less CPU consumption than the previous method.
* Doesn’t fail on circular objects like the previous method.
* If you explicitly make the outer object an instance of a certain class, the entire cloned object will not have prototypal issues.

Cons:
* That’s a shallow copy. Properties holding an object value, will be passed by reference. But that’s a big con if your requirement is deep copy.

Simple custom clone function

function clone(obj){
  if(obj===null || typeof obj !== "object"){
    return obj;
  } else if(Array.isArray(obj)){
    var clonedArr = [];
    obj.forEach(function(element){
      clonedArr.push(clone(element))
    });
    return clonedArr;
  } else{
    let clonedObj = {};
    for(var prop in obj){
      if(obj.hasOwnProperty(prop)){
        clonedObj[prop] = clone(obj[prop]);
      }
    }
    return clonedObj;
  }
}

var newObj = clone(obj);

Pro:
* In depth copy. No pass by reference.
* Less CPU consumption than our first method.
* Works as a utility function.
Con:
* Prototype will be lost. Cloned object will be created from Object.
* In case of circular objects it will throw an error of maximum call stack exceeding.

Better custom clone

function clone(obj){
  //in case of premitives
  if(obj===null || typeof obj !== "object"){
    return obj;
  }

  //date objects should be 
  if(obj instanceof Date){
    return new Date(obj.getTime());
  }

  //handle Array
  if(Array.isArray(obj)){
    var clonedArr = [];
    obj.forEach(function(element){
      clonedArr.push(clone(element))
    });
    return clonedArr;
  }

  //lastly, handle objects
  let clonedObj = new obj.constructor();
  for(var prop in obj){
    if(obj.hasOwnProperty(prop)){
      clonedObj[prop] = clone(obj[prop]);
    }
  }
  return clonedObj;
}

Pros:
* It covers a lot of parts.
* It does a deep copy. No pass by reference.
* All __proto__ chain is intact.
Cons:
* No circular object copy.
* Prototypal properties will have default values.
* It solves the major scenarios, but it still has a list of issues. Check the sections below.

We still have issues

Though the method in the previous section solves our majority issues; but it’s certainly not a perfect one. It has a lot of issues like the following.

  • We’ve set an explicit check for Date & Array type objects. But there are lot others like Map, WeakMap etc. So check for all of them?
  • Even if you put explicit check for these predefined types; there could be more classes defined by you or other developers. Will you put check for all of them?
  • Even if you put checks for all; how will you copy a private properties? They are private to the outside world.
  • We simply considered that all the property names are string. So we focused on cloning only the value parts. But Map & WeakMap holds objects as property.

What’s the solution then?

Well, the best way is probably attaching a clone() function in Object’s prototype and override them wherever necessary. The Object.prototype.clone could be simple as our previous clone functions, but it should be overriden in Array, Date, Map, MyClass, YourClass, HisClass, MyGirlFriendsClass, MyGirlFriendsNailPaintClass or wherever necessary to return the prototypal & private property values properly. You will also be able to handle object keys in Map & WeakMap.

Though a prototypal clone function will cover all our scenarios; but it also has cons.
First con is; this way you will attach an extra function clone in every single type. And you need to remove that explicitly whenever required. Secondly, bad development may cause circular infinite loop; you need to check that.

Now the question is, will you take these headache just to clone an object in JavaScript. Well, if your requirement is like that then you may; but normally the previous methods should be enough to solve the problem.

Future: ESNEXT (ES8 or 9) ways

Warning: Object spread operators have not been standardized yet. In ES6 they standardized it for arrays, but objects are still not done. You need to use babel or other transpilers to make it work.

Using the triple dots or spread operators we can copy objects. However this is a way to do shallow copy. I’m adding this here just to show you a future option.

//clone with spread operators
var oldObj = {
        prop1: "I am value",
        prop2: 200,
        prop3: {
            some: "thing"
        }
}
var clonnedObj = {...oldObj}; //prop3 will be passed by reference
vanilla js

Written By

Paul Shan

Collections

  • E

    ES6

  • R

    React JS

  • C

    CSS

    Cascading style sheets only

  • S

    SEO

    Search engine optimization

  • E

    ES7

  • C

    CMS

    wordpress, drupal, jumla, magento and more


Show All

Tags

vue-js
advanced js
youtube
Web development
vue css
social share buttons
real-life-example
react datetime picker
progra
Online
MathJax
jsfiddle
jquery chart library
instant search using vue
handlebars
event loop
Design
console.log
best practices
imorph

Show All