How to clone a javascript ES6 class instance

Javascriptnode.jsEcmascript 6Es6 Class

Javascript Problem Overview


How do I clone a Javascript class instance using ES6.

I'm not interested in solutions based on jquery or $extend.

I've seen quite old discussions of object cloning that suggest that the problem is quite complicated, but with ES6 a very simple solution presents itself - I will put it below and see if people think it is satisfactory.

edit: it is being suggested that my question is a duplicate; I saw that answer but it is 7 years old and involves very complicated answers using pre-ES6 js. I'm suggesting that my question, which allows for ES6, has a dramatically simpler solution.

Javascript Solutions


Solution 1 - Javascript

It is complicated; I tried a lot! In the end, this one-liner worked for my custom ES6 class instances:

let clone = Object.assign(Object.create(Object.getPrototypeOf(orig)), orig)

It avoids setting the prototype because they say it slows down the code a lot.

It supports symbols but isn't perfect for getters/setters and isn't working with non-enumerable properties (see Object.assign() docs). Also, cloning basic internal classes (like Array, Date, RegExp, Map, etc.) sadly often seems to need some individual handling.

Conclusion: It is a mess. Let's hope that there will one day be a native and clean clone functionality.

Solution 2 - Javascript

const clone = Object.assign( {}, instanceOfBlah );
Object.setPrototypeOf( clone, Blah.prototype );

Note the characteristics of Object.assign: it does a shallow copy and does not copy class methods.

If you want a deep copy or more control over the copy then there are the lodash clone functions.

Solution 3 - Javascript

I like almost all the answers. I had this problem and to resolve it I would do it manually by defining a clone() method and inside it, I would build the whole object from scratch. For me, this makes sense because the resulted object will be naturally of the same type as the cloned object.

Example with typescript:

export default class ClassName {
    private name: string;
    private anotherVariable: string;
   
    constructor(name: string, anotherVariable: string) {
        this.name = name;
        this.anotherVariable = anotherVariable;
    }

    public clone(): ClassName {
        return new ClassName(this.name, this.anotherVariable);
    }
}

I like this solution because it looks more 'Object Oriented'y

Solution 4 - Javascript

TLDR;

// Use this approach
//Method 1 - clone will inherit the prototype methods of the original.
    let cloneWithPrototype = Object.assign(Object.create(Object.getPrototypeOf(original)), original); 

In Javascript it's not recommended to make extensions of the Prototype, It will result in issues when you will make tests on your code/components. The unit test frameworks will not assume automatically yours prototype extensions. So it isn't a good practice. There are more explanations of prototype extensions here https://stackoverflow.com/questions/14034180/why-is-extending-native-objects-a-bad-practice

To clone objects in JavaScript there is not a simple or straightforward way. Here is an the first instance using "Shallow Copy":

1 -> Shallow clone:

class Employee {
	constructor(first, last, street) {
		this.firstName = first;
		this.lastName = last;
		this.address = { street: street };
	}

	logFullName() {
		console.log(this.firstName + ' ' + this.lastName);
	}
}

let original = new Employee('Cassio', 'Seffrin', 'Street A, 23');

//Method 1 - clone will inherit the prototype methods of the original.
let cloneWithPrototype = Object.assign(Object.create(Object.getPrototypeOf(original)), original); 

//Method 2 - object.assing() will not clone the Prototype.
let cloneWithoutPrototype =  Object.assign({},original); 

//Method 3 - the same of object assign but shorter syntax using "spread operator"
let clone3 = { ...original }; 

//tests
cloneWithoutPrototype.firstName = 'John';
cloneWithoutPrototype.address.street = 'Street B, 99'; //will not be cloned

Results:

> original.logFullName(); > > result: Cassio Seffrin > > cloneWithPrototype.logFullName(); > > result: Cassio Seffrin > > original.address.street; > > result: 'Street B, 99' // notice that original sub object was changed

Notice: If the instance has closures as own properties this method will not wrap it. (read more about closures) And plus, the sub object "address" will not get cloned.

> cloneWithoutPrototype.logFullName()

Will not work. The clone won't inherit any of the prototype methods of the original.

> cloneWithPrototype.logFullName()

will work, because the clone will also copy its Prototypes.

To clone arrays with Object.assign:

let cloneArr = array.map((a) => Object.assign({}, a));

Clone array using ECMAScript spread sintax:

let cloneArrSpread = array.map((a) => ({ ...a }));

2 -> Deep Clone:

To archive a completely new object reference we can use JSON.stringify() to parse the original object as string and after parse it back to JSON.parse().

let deepClone = JSON.parse(JSON.stringify(original));

With deep clone the references to address will be keeped. However the deepClone Prototypes will be losed, therefore the deepClone.logFullName() will not work.

3 -> 3th party libraries:

Another options will be use 3th party libraries like loadash or underscore. They will creates a new object and copies each value from the original to the new object keeping its references in memory.

Underscore: let cloneUnderscore = _(original).clone();

Loadash clone: var cloneLodash = _.cloneDeep(original);

The downside of lodash or underscore were the need to include some extra libraries in your project. However they are good options and also produces high performance results.

Solution 5 - Javascript

Create the copy of the object using the same prototype and the same properties as the original object.

function clone(obj) {
  return Object.create(Object.getPrototypeOf(obj), Object.getOwnPropertyDescriptors(obj))
}

Works with non-enumerable properties, getters, setters, etc. Is unable to clone internal slots, which many built-in javascript types have (e.g. Array, Map, Proxy)

Solution 6 - Javascript

if we have multiple class with extends from each other, best solution for clone of each instance is define one function to create new instance of that object in its class definition like :

class Point {
  constructor(x, y) {
    this.x = x;
    this.y = y;
  }
  clone() {
    return new Point(this.x, this.y);
  }
}
class ColorPoint extends Point {
  constructor(x, y, color) {
    super(x, y);
    this.color = color;
  }
  clone() {
    return new ColorPoint(
      this.x, this.y, this.color.clone()); // (A)
  }
}

now i can use clone(0 function of object like :

let p = new ColorPoint(10,10,'red');
let pclone=p.clone();

Solution 7 - Javascript

Try this:

function copy(obj) {
   //Edge case
   if(obj == null || typeof obj !== "object") { return obj; }

   var result = {};
   var keys_ = Object.getOwnPropertyNames(obj);

   for(var i = 0; i < keys_.length; i++) {
       var key = keys_[i], value = copy(obj[key]);
       result[key] = value;
   }

   Object.setPrototypeOf(result, obj.__proto__);

   return result;
}

//test
class Point {
    constructor(x, y) {
        this.x = x;
        this.y = y;
    }
};

var myPoint = new Point(0, 1);

var copiedPoint = copy(myPoint);

console.log(
   copiedPoint,
   copiedPoint instanceof Point,
   copiedPoint === myPoint
);

Since it uses Object.getOwnPropertyNames, it will also add non-enumerable properties.

Solution 8 - Javascript

Another one liner:

Most of the time...(works for Date, RegExp, Map, String, Number, Array), btw, cloning string, number is a bit funny.

let clone = new obj.constructor(...[obj].flat())

for those class without copy constructor:

let clone = Object.assign(new obj.constructor(...[obj].flat()), obj)

Solution 9 - Javascript

class A {
  constructor() {
    this.x = 1;
  }

  y() {
    return 1;
  }
}

const a = new A();

const output =  Object.getOwnPropertyNames(Object.getPrototypeOf(a))
  .concat(Object.getOwnPropertyNames(a))
  .reduce((accumulator, currentValue, currentIndex, array) => {
    accumulator[currentValue] = a[currentValue];
    return accumulator;
  }, {});
  
console.log(output);

enter image description here

Solution 10 - Javascript

Is it not enough to do like this ?

Object.assign(new ClassName(), obj)

Solution 11 - Javascript

I used lodash.

import _ from 'lodash'
class Car {
    clone() { return _.cloneDeep(this); }
}

Solution 12 - Javascript

This is the more complete answer to the OP since there are issues with all of the answers received thus far (not that they won’t work for different cases and scenarios some of the time, they’re just not the simplest universal answers using only ES6 as requested). For posterity.

Object.assign() will only do a shallow copy, as noted by the answer-er. This is actually a huge issue because javascript garbage collection only works when all references are remove from the original object. This means that any cloning done referencing an old object even for a simple bool that rarely changes means a potentially critical memory leak.

Class extends with a “clone()” method has the same garbage collection issues as Object.assign() if you’re creating new instances potentially referencing the old one if even 1 sub-tree of data exists in the object. This would be hard to manage on its own.

Using the spread operator (“...”) is also a shallow copy of arrays/objects, same problems with references and uniqueness as above. In addition, as also mentioned in responses to an answer, this loses the prototype and class anyway

Prototypes are definitely the slower method but V8 I believe has fixed performance issues with that approach so I’m not sure it’s an issue anymore in 2022.

SUGGESTED ANSWER FOR 2022: properly write a deep copy script to get all the class object data. When wanting to clone a class object create a temporary container and do a deep copy of class object into the temporary container. Write a parent class (superclass) with all of the methods in it, and the subclass you want for the object data and instances. Then when calling the parent’s method from the extended subclass, pass in the subclass’s ‘this’ as an argument and catch that argument in the parent’s method (I use the word ‘that’, for eg). Lastly, when you clone the object data into a temporary object, create new instances of all of the objects you want cloned, and replace any reference to the old instance with the new one to make sure it doesn’t linger in memory. In my example I’m making a hacky version of Conway’s Game of Life, for example. I would have an array called “allcells” then when updating it on each requestAnimationFrame(renderFunction) I would deep copy allcells into temp, run each cell’s update(this) method which calls the parent’s update(that) method, then create new Cell(temp[0].x, temp[0].y, etc) and package all those into an array which I can replace my old “allcells” container with after all the updates are done. In the game of life example, without doing the updates in a temp container the former updates would affect the outputs of the latter updates within the same time step, which may be undesirable.

Done! No lodash, no typescript, no jQuery, just ES6 as requested and universal. It looks gnarly, but if you write a generic recursiveCopy() script you could just as easily write a function to use it to make a clone() function if you want to following the steps I outlined above and using the example code below for reference.

function recursiveCopy(arr_obj){
    if(typeof arr_obj === "object") {
        if ( Array.isArray(arr_obj) ) {
            let result = []
            // if the current element is an array
            arr_obj.forEach( v => { result.push(recursiveCopy(v)) } )
            return result 
        }
        else {
            // if it's an object by not an array then it’s an object proper { like: “so” }
            let result = {}
            for (let item in arr_obj) {
                result[item] = recursiveCopy(arr_obj[item]) // in case the element is another object/array
            }
            return result
        }
    }
    // above conditions are skipped if current element is not an object or array, so it just returns itself
    else if ( (typeof arr_obj === "number") || (typeof arr_obj === "string") || (typeof arr_obj === "boolean") ) return arr_obj
    else if(typeof arr_obj === "function") return console.log("function, skipping the methods, doing these separately")
    else return new Error( arr_obj ) // catch-all, likely null arg or something
}

// PARENT FOR METHODS
class CellMethods{
    constructor(){
        this.numNeighboursSelected = 0
    }

    // method to change fill or stroke color
    changeColor(rgba_str, str_fill_or_stroke, that) {
        // DEV: use switch so we can adjust more than just background and border, maybe text too
        switch(str_fill_or_stroke) {
        case 'stroke':
            return that.border = rgba_str
        default:      // fill is the default
            return that.color = rgba_str
        }
    }

    // method for the cell to draw itself
    drawCell(that){
        // save existing values
        let tmp_fill = c.fillStyle
        let tmp_stroke = c.strokeStyle
        let tmp_borderwidth = c.lineWidth
        let tmp_font = c.font
        
        // fill and stroke cells
        c.fillStyle = (that.isSelected) ? highlightedcellcolor : that.color
        c.strokeStyle = that.border
        c.lineWidth = border_width
        c.fillRect(that.x, that.y, that.size.width, that.size.height)
        c.strokeRect(that.x, that.y, that.size.width+border_width, that.size.height+border_width)
        
        // text id labels
        c.fillStyle = that.textColor
        c.font = `${that.textSize}px Arial`
        c.fillText(that.id, that.x+(cellgaps*3), that.y+(that.size.height-(cellgaps*3)))
        c.font = tmp_font

        // restore canvas stroke and fill
        c.fillStyle = tmp_fill
        c.strokeStyle = tmp_stroke
        c.lineWidth = tmp_borderwidth    
    }
    checkRules(that){
        console.log("checking that 'that' works: " + that)
        if ((that.leftNeighbour !== undefined) && (that.rightNeighbour !== undefined) && (that.topNeighbour !== undefined) && (that.bottomNeighbour !== undefined) && (that.bottomleft !== undefined) && (that.bottomright !== undefined) && (that.topleft !== undefined) && (that.topright !== undefined)) {
            that.numNeighboursSelected = 0
            if (that.leftNeighbour.isSelected) that.numNeighboursSelected++
            if (that.rightNeighbour.isSelected) that.numNeighboursSelected++
            if (that.topNeighbour.isSelected) that.numNeighboursSelected++
            if (that.bottomNeighbour.isSelected) that.numNeighboursSelected++
            // // if my neighbours are selected
            if (that.numNeighboursSelected > 5) that.isSelected = false
        }
    }
}

// write a class to define structure of each cell
class Cell extends CellMethods{
    constructor(id, x, y, selected){
        super()
        this.id = id
        this.x = x
        this.y = y
        this.size = cellsize
        this.color = defaultcolor
        this.border = 'rgba(0,0,0,1)'
        this.textColor = 'rgba(0,0,0,1)'
        this.textSize = cellsize.height/5     // dynamically adjust text size based on the cell's height, since window is usually wider than it is tall
        this.isSelected = (selected) ? selected : false
    }
    changeColor(rgba_str, str_fill_or_stroke){ super.changeColor(rgba_str, str_fill_or_stroke, this)} // THIS becomes THAT
    checkRules(){ super.checkRules(this) } // THIS becomes THAT
    drawCell(){ super.drawCell(this) } // THIS becomes THAT
}

let [cellsincol, cellsinrow, cellsize, defaultcolor] = [15, 10, 25, 'rgb(0,0,0)'] // for building a grid
// Bundle all the cell objects into an array to pass into a render function whenever we want to draw all the objects which have been created
function buildCellTable(){
    let result = []  // initial array to push rows into
    for (let col = 0; col < cellsincol; col++) {  // cellsincol aka the row index within the column
    let row = []
    for (let cellrow = 0; cellrow < cellsinrow; cellrow++) {  // cellsinrow aka the column index
        let newid = `col${cellrow}_row${col}` // create string for unique id's based on array indices
        row.push( new Cell(newid, cellrow*(cellsize.width),col*(cellsize.height) ))
    }
    result.push(row)
    }
    return result
}

// poplate array of all cells, final output is a 2d array
let allcells = buildCellTable()

// create hash table of allcells indexes by cell id's
let cellidhashtable = {}
allcells.forEach( (v,rowindex)=>{
    v.forEach( (val, colindex)=>{
    cellidhashtable[val.id] = [rowindex, colindex]  // generate hashtable 
    val.allcellsposition = [rowindex, colindex]     // add cell indexes in allcells to each cell for future reference if already selected    
    } )
})

// DEMONSTRATION
let originalTable = {'arr': [1,2,3,4,5], 'nested': [['a','b','c'], ['d','e','f']], 'obj': {'nest_obj' : 'object value'}}
let newTable = recursiveCopy(originalTable) // works to copy
let testingDeepCopy = recursiveCopy(newTable)
let testingShallowCopy = {...newTable}  // spread operator does a unique instance, but references nested elements
newTable.arr.pop() // removes an element from a nested array after popping
console.log(testingDeepCopy)   // still has the popped value
console.log(testingShallowCopy)  // popped value is remove even though it was copies before popping

// DEMONSTRATION ANSWER WORKS
let newCell = new Cell("cell_id", 10, 20)
newCell.checkRules()

Solution 13 - Javascript

You can use spread operator, for instance if you want to clone an object named Obj:

let clone = { ...obj};

And if you want to change or add anything to the cloned object:

let clone = { ...obj, change: "something" };

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionTomView Question on Stackoverflow
Solution 1 - JavascriptfloriView Answer on Stackoverflow
Solution 2 - JavascriptTomView Answer on Stackoverflow
Solution 3 - JavascriptHSLMView Answer on Stackoverflow
Solution 4 - JavascriptCassio SeffrinView Answer on Stackoverflow
Solution 5 - JavascriptTheNumberOneView Answer on Stackoverflow
Solution 6 - JavascriptMohammad DaneshamoozView Answer on Stackoverflow
Solution 7 - JavascriptNirvanaView Answer on Stackoverflow
Solution 8 - JavascriptEricView Answer on Stackoverflow
Solution 9 - JavascriptAlex IvasyuvView Answer on Stackoverflow
Solution 10 - JavascriptDannyView Answer on Stackoverflow
Solution 11 - JavascriptGilbertView Answer on Stackoverflow
Solution 12 - JavascriptKris DriverView Answer on Stackoverflow
Solution 13 - JavascriptttfreemanView Answer on Stackoverflow