Clone an object in vanilla JS - multiple ways

Wed Mar 08 2017

Paul Shan

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

SHARE THIS ARTICLE

post-thumbnail
Today everyone knows the importance of a lightning-fast website and how the speed impacts the conversion rate of a business. Today, everyone wants the site to be a PWA so that the mobile users can have an app-like experience with the website because, for the majority of the merchants, the customers come through mobile devices.
Tue Apr 20 2021
post-thumbnail
Here we are going to see how you can manage backup and restore of Postgres database with docker.
Thu Sep 03 2020
post-thumbnail
Image sliders or carousels always have increased the UI attraction of websites and they are pretty useful for reflecting the major roles/products too. In case, I am having a website that sells tee-shirts,
Mon Apr 30 2018

About VoidCanvas

This blog was created out of hobby and talks mostly about technology, web development, JavaScript, NodeJS and related topics. Thank you for reading my blog.

Copyright 2022 - www.voidcanvas.com

Popular Articles

Authentication using Google's oAuth api with node.js

Thu Mar 10 2016

OAuth authentications are pretty popular now a days and another thing which is popular is JavaScript. This article shows how to plugin google’s oAuth api for authentication in your own node application.

CSS3 Loader Snippet Collection: (Part 2 - Squares)

Sat Mar 01 2014

This is a continuation of my CSS3 loader snippet collection series. I've provided spinning css3 animation loader in the part 1 of this series and here in part 2, I'm providing various square type loading