146

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.

flori
  • 14,339
  • 4
  • 56
  • 63
Tom
  • 17,103
  • 8
  • 67
  • 75
  • 4
    If you have a new answer for an old question on Stack Overflow, please add that answer to the original question, don't just create a new one. – Heretic Monkey Jan 04 '17 at 23:41
  • 1
    I do see the problem Tom is/was facing since ES6 class instances work different from "regular" Objects. – CherryNerd Jan 04 '17 at 23:48
  • 2
    Also, the first piece of code in the accepted answer your "possible duplicate" provides actually crashes when I try to run it over an instance of an ES6 class – CherryNerd Jan 04 '17 at 23:59
  • I think this is not a duplicate, because although ES6 class instance is an object, not every object is ES6 class instance and therefore the other question does not address this question's issue. – Tomáš Zato Oct 28 '17 at 17:27
  • 7
    It is not a duplicate. The other question was about pure `Object`s used as data holders. This one is about ES6 `class`es and the problem to not lose the class type information. It needs a different solution. – flori Oct 28 '17 at 19:54
  • Casting a second reopen vote. Class instances are not necessarily the same as plain objects. – jhpratt Sep 21 '18 at 00:38
  • This is not a duplicate at all. At least not to the linked question. Voting for reopen – vir us May 30 '19 at 20:47

13 Answers13

187

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.

Adrian Mole
  • 49,934
  • 160
  • 51
  • 83
flori
  • 14,339
  • 4
  • 56
  • 63
  • 2
    This won't copy static methods because they are not actually enumerable own properties. – Mr. Lavalamp Dec 22 '17 at 06:26
  • 7
    @Mr.Lavalamp and how can you copy (also) the static methods? – flori Dec 23 '17 at 12:53
  • this will destroy the arrays! It'll convert all arrays to objects with "0","1",... keys. – Vahid Oct 19 '18 at 10:29
  • @vmoh_ir how to deal with this? – Kesha Antonov Oct 21 '18 at 14:26
  • 1
    @KeshaAntonov You may be able to find a solution with typeof and Array methods. I myself prefered to clone all properties manually. – Vahid Oct 21 '18 at 18:24
  • 4
    Do not expect it to clone properties that are themselves objects: https://jsbin.com/qeziwetexu/edit?js,console – jduhls Nov 13 '18 at 20:12
  • 2
    Static method not need to be cloned! they part of the Class not the instance – pery mimon Jan 10 '19 at 09:33
  • This thing will make `orig` a prototype of newly created object. Consider doing `Object.create(orig.__proto__)` instead of `Object.create(orig)`, or if you repeat this 100 times, you will end with 100+-long prototype chain. – Heimdell Jan 26 '19 at 15:38
  • @Heimdell Actually it does already do `Object.create(orig.getPrototypeOf())` which is equal to your `Object.create(orig.__proto__)` – flori Jan 26 '19 at 21:43
  • Does not copy non-enumerable properties, does not deal well with getters and setters, does not copy symbol properties, does not clone non-primitive members (so they actually get to share their data), does not work with a `Date`, `RegExp`, `Set`, `Map`, ... If `orig = [1,2,3]`, then `clone.length === 0`. Not running the constructor is problematic in many cases. – trincot Dec 29 '19 at 14:29
  • @trincot Thanks, I added notes regarding your mentioned limitations. Symbols seems to work fine(?) Shallow cloning is, as I would say, as expected(?) – flori Dec 30 '19 at 21:24
  • *it slows down the code a lot* this is premature optimisation. It's unlikely that the "a lot" is measurable unless your cloning millions of objects a second. – Liam Aug 03 '22 at 08:49
  • I think *private* members of the class are not copied too?... – Nor.Z Feb 16 '23 at 04:14
26
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.

Tom
  • 17,103
  • 8
  • 67
  • 75
  • 3
    Since `Object.create` creates new object with specified prototype, why not then just `const clone = Object.assign(Object.create(instanceOfBlah), instanceOfBlah)`. Also class methods will be copied as well. – barbatus Feb 19 '17 at 11:21
  • 3
    @barbatus That uses the wrong prototype though, `Blah.prototype != instanceOfBlah`. You should use `Object.getPrototypeOf(instanceOfBlah)` – Bergi Jul 15 '17 at 01:56
  • 1
    @Bergi no, ES6 class instance doesn't always have a prototype. Check out https://codepen.io/techniq/pen/qdZeZm that it works with the instance too. – barbatus Sep 02 '17 at 07:52
  • 1
    @barbatus Sorry, what? I don't follow. All instances have a prototype, that's what makes them instances. Try the code from flori's answer. – Bergi Sep 02 '17 at 08:55
  • 1
    @Bergi I think it depends on the Babel configuration or something. I am right now implementing a reactive native app and instances with no inherited properties have prototype null there. Also as you can see here https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/getPrototypeOf it's possible that getPrototypeOf returns null. – barbatus Sep 02 '17 at 08:59
  • @barbatus But never for an object that was instantiated with `new` from a normal `class`. And even then, you would want the clone to have the same prototype… – Bergi Sep 02 '17 at 13:56
  • @Bergi how are then prototypes of objects created by `Object.create(instance)` and `Object.create(Object.getPrototypeOf(instance))` different (given that it's for cloning)? – barbatus Sep 06 '17 at 15:01
  • 1
    @barbatus Isn't it obvious that they're different? If you use the instance, and then clone a clone of a clone of a clone etc., you get an infinitely long prototype chain for the last of them which includes all the others. They will behave totally different from clones that just use the original same prototype. – Bergi Sep 06 '17 at 18:19
  • How about `Object.assign(Object.create(Object.getPrototypeOf(instanceOfBlah)), instanceOfBlah)`? It doesn't do a deep clone, but it works great for most purposes. – Toothbrush Feb 17 '18 at 13:20
  • @Bergi can you please provide full answer how this answer should have been to be correct? – T.Todua Dec 08 '21 at 20:37
  • @T.Todua Just what Toothbrush suggested, or the accepted answer from flori. You can exchange `Object.getPrototypeOf(instanceOfBlah)` for `Blah.prototype` if you know the class of the object to be cloned. – Bergi Dec 08 '21 at 20:57
  • @Bergi but Toothbrush said `..It doesn't do a deep clone ..` so, is that true? I thought your solution was aiming to do a deep clone. – T.Todua Dec 09 '21 at 07:46
  • @T.Todua None of the solutions here do a deep clone – Bergi Dec 09 '21 at 09:35
19

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

HSLM
  • 1,692
  • 10
  • 25
  • 5
    This is indeed the way forward. It's *very* hard to get a cloning mechanism that works generically. It's impossible to get it working right for every single case. There are always going to be weird and inconsistent classes. So, ensuring your objects themselves are cloneable is the only way to be sure. As an alternative (or addition) it's possible to have a method that does the cloning *from* an instance, something like `public static clone(instance: MyClass): MyClass)` which has the same idea of handling cloning specifically just making it external to the instance. – VLAZ Nov 25 '21 at 13:56
  • 3
    This is a great answer, and the comment above is a great suggestion. It is also worth pointing out that object and array properties get passed by reference, so you'd need to clone those as well or risk suffering unexpected side effects! Here is a gist for illustration: https://gist.github.com/sscovil/def81066dc59e6ff5084a499d9855253 – Shaun Scovil Feb 22 '22 at 21:02
  • Best answer here! – Frederik Krautwald Jul 06 '23 at 23:48
4

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 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.

Cassio Seffrin
  • 7,293
  • 1
  • 54
  • 54
  • It would be better if you'd [edit] your [previous answer](https://stackoverflow.com/a/59489960/1048572) instead of deleting and reposting it. – Bergi Dec 31 '19 at 13:31
  • 3
    When assigning to `{}`, the clone won't inherit any of the prototype methods of the original. `clone.logFullName()` will not work at all. The `Object.assign( Object.create(Object.getPrototypeOf(eOriginal)), eOriginal)` you had before was fine, why did you change that? – Bergi Dec 31 '19 at 13:32
  • 1
    @Bergi thks for your contribution, I was editing the my answer right now, I added your point to copy the prototypes! – Cassio Seffrin Dec 31 '19 at 13:43
  • 2
    I appreciate your help @Bergi, Please let your opinion now. I have finished the edition. I think now the answer have covered almost all the question. Thks! – Cassio Seffrin Dec 31 '19 at 13:50
  • They are the same of Object.assign({},original), but with a shorter sintax, I think now all the options were exposed in the answer. – Cassio Seffrin Dec 31 '19 at 15:15
  • 1
    Yes, and just like `Object.assign({},original)`, it does not work. – Bergi Dec 31 '19 at 15:36
  • 2
    sometimes the simpler approach is all we need. If you don't need Prototypes and complex objects may just "clone = { ...original }" could solve the problem – Cassio Seffrin Dec 31 '19 at 19:39
  • IMO lodash is the best option for cloning Objects in Javascript. However in Vanilla JS I will choose the cloneWithoutPrototype method as described in this answer –  Jan 20 '21 at 03:20
2

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)

TheNumberOne
  • 404
  • 3
  • 13
  • 2
    This is a good approach as it delegates a lot of the processing needed for all of this to JavaScript. However, it has an issue with any potential object values, as they would be shared between the original and the cloned object. For example, an array value will be updated by both instances. – VLAZ Nov 25 '21 at 13:36
2

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();
0

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.
Nirvana
  • 405
  • 3
  • 15
-2

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)
Eric
  • 365
  • 3
  • 6
  • `fn(...[obj].flat())` === `fn(obj)` there is no real reason for the extra 1. array, 2. flattening into an array with a single single member. 3. Spreading that single member into one argument. Even then, this only works on types with a copy constructor. The second version does not necessarily work with classes that don't have a copy constructor - it might even cause an error, consider `constructor(a, b) { this.c = a + b }` which normally expects numbers but gets an instance of itself for `a` and `undefined` for `b`. – VLAZ Nov 25 '21 at 13:52
-2

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

VLAZ
  • 26,331
  • 9
  • 49
  • 67
Alex Ivasyuv
  • 8,585
  • 17
  • 72
  • 90
  • 1
    Two issues here - 1. this loses the class information - `output instanceof A` is `false`. 2. The cloning is only one level up the prototype chain, if there is a `class B extends A { b() { return 2; }}` and `class C extends B { c() { return 3; }}` then "cloning" an instance of `C` ends up only copying `b()` and `c()` but not the properties of `A` (`y`). The property `x` is going to be copied only because it's set in the constructor directly on the instance. – VLAZ Nov 25 '21 at 13:46
-2

Is it not enough to do like this ?

Object.assign(new ClassName(), obj)
Danny
  • 305
  • 3
  • 12
  • It depends on the class. If it's something simple, this may be enough. But that about the code in the constructor? What does it do and do you want it to run when you clone this object? What about closures, like arrow functions? these you can't copy or `this` will point to the old instance, then there are private fields, ... a lot of pitfalls – Thomas Jan 07 '22 at 11:45
  • ok I'm using like this and I guess it's enough in my case – Danny Jan 17 '22 at 15:32
-2

I used lodash.

import _ from 'lodash'
class Car {
    clone() { return _.cloneDeep(this); }
}
Gilbert
  • 2,699
  • 28
  • 29
-4

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()
  • "*a shallow copy […] is actually a huge issue because javascript garbage collection […] means a potentially critical memory leak*" - nope, absolutely not. – Bergi Apr 21 '22 at 17:22
  • Example: obj = { arr: ['a', 'b', 'c'], boole: 'false } newobj = {...} even if obj is never used again and newobj.a is never changed, it's still a reference to the old instance which has to linger in memory. You can see this in the example in the demonstration portion of the code I posted at the bottom showing this is the case in a console log. – Kris Driver Apr 21 '22 at 17:41
  • Nope. There is no reference from `newObj` to `obj`. Sure, both contain a reference to the same array, but that does not prevent garbage collection of `obj` in any way. – Bergi Apr 21 '22 at 18:12
  • Did you even look at the console logs I put in my code? the reference stays open until the original object is cleared, and even then it depends on the engine whether or not it gets garbage collected. That's the "shallow" part of "shallow copy". It only makes new instances (ie not references) of the top-most level, not nested objects or nested arrays. I said "potentially critical", because bugs without errors are hardest to debug. My English is fine and the answer is accurate. – Kris Driver Apr 21 '22 at 18:17
  • Are you talking about references to the nested array or references to the old object? What do you mean by "*the original object is cleared*"? Are you talking about bugs caused by inadvertent sharing, or are you talking about memory leaks? Because no, shallow copying does not cause memory leaks, that's simply wrong. – Bergi Apr 21 '22 at 18:24
  • newobj = {...oldobj} <= if oldobj has a nested object or array then it can be changed later and affect the newobj values in the nested elements. It sits there in memory in perpetuity waiting to cause a bug if you only perform a shallow copy of it and that's it. It doesn't get garbage collected. That's precisely what shallow copy means, deep elements are references and remain so. Either way, the example works and explains in detail why this is an important consideration, and it answers the OP. What's the issue? The word "critical" or "potentially"? – Kris Driver Apr 21 '22 at 18:29
  • It seems you're not understanding how nested objects work together with garbage collection. No, `oldobj` does not sit around "waiting to be changed". The `oldobj` never is changed, it is no longer referenced anywhere, it'll get garbage-collected. What's being changed in your code is the array, which sits at a different position in memory, and is referenced from both the `oldobj` and `newobj`. – Bergi Apr 21 '22 at 18:33
  • "*What's the issue? The word "critical" or "potentially"?*" - no, the word "memory leak" and the mentioning of garbage collection altogether. – Bergi Apr 21 '22 at 18:34
  • Also the advice "*Write a parent class (superclass) with all of the methods in it, and the subclass you want for the object data and instances*" makes no sense. That's not how you should design classes. And what's worse, your proposed `recursiveCopy` function doesn't even work when applied to a `Cell` instance. – Bergi Apr 21 '22 at 18:35
  • Like I said in the post, you're not supposed to apply the recursive copy to the cell instance, you create a copy object then recreate the cell instances from the deeply copied objects to transfer the data over. If you google search memory leak you'll find definitions, and included is a stale instance that can't be collected because of a hanging reference to it. It's not leak-ING, it is a leak. Memory no longer required by the program but not returned to the operating system is literally a leak. – Kris Driver Apr 25 '22 at 10:51
  • But that's not what the OP wants. Also, if you don't believe me, please just run the code yourself and do memory snapshots. `oldobj` *is* garbage-collected, neither is there a "hanging reference" to it nor is there a leak. – Bergi Apr 25 '22 at 10:58
  • I am running it, right now, on a cellular automata problem. OP is: "How do I clone a Javascript class instance using ES6. I'm not interested in solutions based on jquery or $extend." The way to do this with deeply nested classes is not Object.assign, it is by making a deep copy of the object data, making new class objects, then replacing any references to the original objects for them to be garbage collected. I'm running it right now, I can show you it is working as I described. check my git, life6-1.htm if you want. I'll upload it just for you. – Kris Driver Apr 26 '22 at 16:42
  • I've uploaded it and it runs just fine. it's in my git html-tools folder called life6-1.htm It's just a sketch, the CA isn't done properly, but it's a demo of the concepts I'm describing. I'm not going to engage anymore since you don't seem interested in actually responding to the things I am writing. I keep repeating the same thing and you keep ignoring the detail which I repeated even in my original answer. You can look up "memory leak" and "deep copy" if you're actually interested, but I can't explain it any better than I already have. There are definitions for terms we should agree on. – Kris Driver Apr 26 '22 at 16:50
  • I never wrote that your example code doesn't run (although there certainly are problems with it), I'm taking an issue with your explanation. I don't know why you keep repeating that shallow-copying objects leads to memory leaks, without any evidence (such as a heap dump comparison) or explanation where the reference hangs from. This is not an issue with the definition of the term. – Bergi Apr 26 '22 at 17:12
  • I explained it in my answer. Again, if you google search memory leak it will explain why making a deep copy of the class to a temporary object, then creating new class object instances using the data from the temp data object instead of the old class object data because should there be anything nested in that class object, it will point to a reference of the old instance and leave it hanging in memory despite no longer being needed. That is part of the definition of memory leak. That's why, the evidence is the definition. I can't explain better than definition of terms. – Kris Driver Apr 28 '22 at 14:49
  • "*it will point to a reference of the old instance and leave it hanging in memory despite no longer being needed.*" - but that's simply wrong. Either it's still needed (because it's now part of the new instance), or it's not pointed at (and will be garbage-collected). Are you talking of a reference pointing to the old instance itself, or a reference pointing to the nested object inside the old instance? – Bergi Apr 28 '22 at 23:08
  • If any part of the old object is a reference, the entire object will need to remain in memory to maintain that reference. So any object with nested objects needs to be deep copied for the garbage collection to occure, despite the programmer no longer needing the old object and believing it was fully copied. That's a memory leak, because another copy can occur on the new object, leaving it in limbo as a reference because of a single branch in its tree being referenced instead of copied. – Kris Driver Nov 27 '22 at 17:43
  • "*If any part of the old object is a reference, the entire object will need to remain in memory to maintain that reference.*" - no, absolutely not. – Bergi Nov 27 '22 at 18:33
  • There are 2 javascript garbage collection algorithms: "mark and sweep" and "reference counting". You can look it up and let me know which of those two can garbage collect on a class object nested value if it's referenced by a new object that was only shallow copied. Use your words if you know better, I've explained it thoroughly and you're just trolling my response without any substantive input to the contrary. You're not helping the community right here, you're just trying to troll to protect your own ego. It's as sad as it is frustrating. – Kris Driver Nov 29 '22 at 03:15
  • I've stopped trying to convince you, I'm just trying to protect the community from blatantly false statements - or what must be a horrible misunderstanding. Everyone can check for themselves whether a garbage collector will preserve objects that *contain* a reference to a live object (as you claim) or only those that *are referenced* from live objects. It's the same for mark'n'sweep and refcounting. – Bergi Nov 29 '22 at 12:57
-5

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" };
ttfreeman
  • 5,076
  • 4
  • 26
  • 33
  • This loses the tprototype information, which includes the class. If `obj = new A()`, then `clone instanceof A` is `false`. Which also means that methods are lost, as would any other non-enumerable properties the instance might have. – VLAZ Nov 25 '21 at 13:29