168

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
16.1k5 gold badges59 silver badges65 bronze badges
asked Jan 4, 2017 at 23:30
7
  • 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. Commented Jan 4, 2017 at 23:41
  • 1
    I do see the problem Tom is/was facing since ES6 class instances work different from "regular" Objects. Commented Jan 4, 2017 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 Commented Jan 4, 2017 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. Commented Oct 28, 2017 at 17:27
  • 7
    It is not a duplicate. The other question was about pure Objects used as data holders. This one is about ES6 classes and the problem to not lose the class type information. It needs a different solution. Commented Oct 28, 2017 at 19:54

15 Answers 15

212

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
52.1k193 gold badges61 silver badges100 bronze badges
answered Jun 27, 2017 at 13:49
13
  • 3
    This won't copy static methods because they are not actually enumerable own properties. Commented Dec 22, 2017 at 6:26
  • 7
    @Mr.Lavalamp and how can you copy (also) the static methods? Commented Dec 23, 2017 at 12:53
  • 1
    @KeshaAntonov You may be able to find a solution with typeof and Array methods. I myself prefered to clone all properties manually. Commented Oct 21, 2018 at 18:24
  • 5
    Do not expect it to clone properties that are themselves objects: jsbin.com/qeziwetexu/edit?js,console Commented Nov 13, 2018 at 20:12
  • 5
    Static method not need to be cloned! they part of the Class not the instance Commented Jan 10, 2019 at 9:33
35

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

answered Nov 26, 2020 at 15:13
3
  • 8
    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. Commented Nov 25, 2021 at 13:56
  • 6
    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: gist.github.com/sscovil/def81066dc59e6ff5084a499d9855253 Commented Feb 22, 2022 at 21:02
  • This does indeed look object oriented-y. Aka overly verbose and 'repeat yourself 4 times' on extension. Would be great if typescript just offered proper dataclasses that implement clone by default Commented Sep 20, 2023 at 10:37
25
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.

answered Jan 4, 2017 at 23:30
13
  • 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. Commented Feb 19, 2017 at 11:21
  • 3
    @barbatus That uses the wrong prototype though, Blah.prototype != instanceOfBlah. You should use Object.getPrototypeOf(instanceOfBlah) Commented Jul 15, 2017 at 1:56
  • 1
    @Bergi no, ES6 class instance doesn't always have a prototype. Check out codepen.io/techniq/pen/qdZeZm that it works with the instance too. Commented Sep 2, 2017 at 7:52
  • 2
    @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. Commented Sep 2, 2017 at 8: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 developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/… it's possible that getPrototypeOf returns null. Commented Sep 2, 2017 at 8:59
5

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.

answered Dec 27, 2019 at 11:30
8
  • 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? Commented Dec 31, 2019 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! Commented Dec 31, 2019 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! Commented Dec 31, 2019 at 13:50
  • 1
    Yes, and just like Object.assign({},original), it does not work. Commented Dec 31, 2019 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 Commented Dec 31, 2019 at 19:39
3

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)

answered Feb 27, 2021 at 1:36
1
  • 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. Commented Nov 25, 2021 at 13:36
2

Use this.contructor to clone a class instance.

new this.constructor(...constructorArgs)

Full example:

class Base {
 constructor(value) {
 this.value = value;
 }
 
 clone() {
 return new this.constructor(this.value)
 }
}
class SubClass extends Base {}
obj = new SubClass(7);
clone = obj.clone();
console.log(clone instanceof SubClass); // true
console.log(clone.value); // 7
console.log(clone === obj); // false

Note: This will only clone values passed to the constructor, or values derived from the same.

answered Aug 27, 2024 at 9:56
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.

answered Apr 19, 2021 at 7:33
0
0

When your instance has private properties that are essential for the state of an instance, then the solutions based on Object.create and Object.assign will not copy those, and there is also no way to define those on the so-created object later (see TypeError: can't access/set private field or method: object is not the right class). There is no way around it: the constructor must be called in order to get those private properties defined.

Other solutions do call the constructor, but they assume that the arguments to that constructor can be derived from the source object, but that is not always the case.

Example of class with a private property

Here is an example class where it is thus a challenge to provide a faithful clone of an instance. This example implements a simple BigDecimal class where the constructor takes a value that can be a number of a string representation of a number. It uses a private BigInt property, which represents the decimal value, but multiplied by a power of 10. The end user of this class has no business with that bigint value, which would only lead to confusion, so it is private:

class BigDecimal {
 // #n: the BigInt that will hold the BigDecimal's value, but
 // multiplied by 1e18 so to retain 18 decimal digits.
 #n; 
 constructor(value) {
 if (value instanceof BigDecimal) return value;
 // Extract the integer and fractional parts from string
 const [ints, decis] = (value+".").split(".");
 // And build the private bigint from it
 this.#n = BigInt(ints + (decis + "0".repeat(18)).slice(0, 18));
 }
 static stringify(n) {
 // Insert the decimal point in the string representation of n
 return n.toString()
 .replace(/(?=\d)/, "0".repeat(19))
 .replace(/(?=\d{18}$)/, ".")
 .replace(/(?<=^|-)0+\B|(\.?0*$)/g, "");
 }
 toString() {
 return BigDecimal.stringify(this.#n);
 }
}
// Demo
const a = new BigDecimal("-1000000000001.000000000555");
console.log("a = " + a);

The challenge here is that an instance of this BigDecimal does not have enumerable properties, and its state depends solely on a private property.

If now we need to create a new instance that represents the sum of two other instances, like what an add method would return, then Object.create will not help us: we will not have the private property. The only way to get the private property is to create an instance via the constructor. In this example, to use the constructor we need a string representation of the sum. That is doable, and looks like this:

 add(value) {
 const operand = new BigDecimal(value);
 const intSum = this.#n + operand.#n;
 return new BigDecimal(BigDecimal.stringify(intSum));
 }

But it is a pity we have to convert the bigint value to string and then have the constructor parse it back to a bigint number, which we already had.

Solution

It would be great if we could pass the to-be-private bigint to the constructor, but at the same time we don't want users of this class to use that feature, since that bigint is not the value of the big decimal, but a multiple of it. That would be confusing.

Once way to achieve that is to allow for an additional, optional, parameter that is unusable for "external" users of the class, but when provided indicates that we want to set private property(ies) of the instance via argument(s). The value of that parameter could be the reference of a private method, which of course an external user has no access to:

class BigDecimal {
 // #n: the BigInt that will hold the BigDecimal's value, but
 // multiplied by 1e18 so to retain 18 decimal digits.
 #n; 
 constructor(value, method) {
 if (value instanceof BigDecimal) return value;
 // Initialise the private property if it was given:
 if (method === this.#setPrivate) return this.#setPrivate(value);
 // Extract the integer and fractional parts from string
 const [ints, decis] = (value+".").split(".");
 // And build the private bigint from it
 this.#n = BigInt(ints + (decis + "0".repeat(18)).slice(0, 18));
 }
 #setPrivate(n) {
 this.#n = n;
 return this;
 }
 add(value) {
 const other = new BigDecimal(value);
 return new BigDecimal(this.#n + other.#n, this.#setPrivate);
 }
 static stringify(n) {
 // Insert the decimal point in the string representation of n
 return n.toString()
 .replace(/(?=\d)/, "0".repeat(19))
 .replace(/(?=\d{18}$)/, ".")
 .replace(/(?<=^|-)0+\B|(\.?0*$)/g, "");
 }
 toString() {
 return BigDecimal.stringify(this.#n);
 }
}
// Demo
const sum = (new BigDecimal("-1000000000001.000000000555" ))
 .add( "3213123213123.3232000000008");
console.log("sum " + sum);

Although this is a specific example, I believe it illustrates how we could clone instances from a class that declares private properties.

answered Mar 5 at 15:49
0

I developed a function to clone objects and child objects as needed

/**
 * Clones an instance (on all levels by default)
 * @param instance - The instance to be cloned
 * @param levels - [=Infinity] The levels that should be cloned
 * @returns The cloned instance
 */
function clone (instance, levels = Infinity) {
 const cloneInstance = Array.isArray(instance)
 ? Object.assign([], instance)
 : Object.assign(Object.create(Object.getPrototypeOf(instance)), instance)
 if (levels > 0) {
for (const key in cloneInstance) {
 if (Object.prototype.hasOwnProperty.call(cloneInstance, key)) {
 if (typeof cloneInstance[key] === 'object') {
 cloneInstance[key] = clone(cloneInstance[key], levels - 1)
 }
 }
}
 }
 return cloneInstance
}
class TesterGrandChildClass {
 constructor (id, name) {
this.id = id
this.name = name
 }
}
class TesterChildClass {
 constructor (id, name, childId, childName) {
this.id = id
this.name = name
this.child = new TesterGrandChildClass(childId, childName)
 }
}
class TesterClass {
 constructor (id, name, childId, childName, grandChildId, grandChildName) {
this.id = id
this.name = name
this.child = new TesterChildClass(childId, childName, grandChildId, grandChildName)
 }
}
const base = new TesterClass(1, 'name', 1, 'child', 2, 'grandChild')
const cloneAll = clone(base)
const cloneFirst = clone(base, 0)
const cloneSecond = clone(base, 1)
console.log('base === cloneAll:', base === cloneAll)
console.log('base.child === cloneAll.child:', base.child === cloneAll.child)
console.log('base.child.child === cloneAll.child.child:', base.child.child === cloneAll.child.child)
console.log('base === cloneFirst:', base === cloneFirst)
console.log('base.child === cloneFirst.child:', base.child === cloneFirst.child)
console.log('base.child.child === cloneFirst.child.child:', base.child.child === cloneFirst.child.child)
console.log('base === cloneSecond:', base === cloneSecond)
console.log('base.child === cloneSecond.child:', base.child === cloneSecond.child)
console.log('base.child.child === cloneSecond.child.child:', base.child.child === cloneSecond.child.child)
const baseWithArray = { id: 1, child: [] }
const cloneInf = clone(baseWithArray)
clone1 = clone(baseWithArray, 0)
const data = { test: true }
baseWithArray.child.push(data)
console.log('Base child')
console.log(baseWithArray.child)
console.log('cloneInf child')
console.log(cloneInf.child)
console.log('clone1 child')
console.log(clone1.child)

Here's it already in TypeScript

/**
 * Clones an instance (on all levels by default)
 * @param instance - The instance to be cloned
 * @param levels - [=Infinity] The levels that should be cloned
 * @returns The cloned instance
 */
function clone <T> (instance: T, levels = Infinity): T {
 const cloneInstance = Array.isArray(instance)
 ? Object.assign([], instance)
 : Object.assign(Object.create(Object.getPrototypeOf(instance)), instance)
 if (levels > 0) {
 for (const key in cloneInstance) {
 if (Object.prototype.hasOwnProperty.call(cloneInstance, key)) {
 if (typeof cloneInstance[key] === 'object') {
 cloneInstance[key] = this.clone(cloneInstance[key], levels - 1)
 }
 }
 }
 }
 return cloneInstance
}
answered May 21 at 9:31
-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)
answered Jul 23, 2020 at 5:13
1
  • 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. Commented Nov 25, 2021 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
29.6k9 gold badges65 silver badges87 bronze badges
answered Aug 17, 2020 at 18:16
1
  • 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. Commented Nov 25, 2021 at 13:46
-3

Is it not enough to do like this ?

Object.assign(new ClassName(), obj)
answered Jan 7, 2022 at 11:29
2
  • 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 Commented Jan 7, 2022 at 11:45
  • ok I'm using like this and I guess it's enough in my case Commented Jan 17, 2022 at 15:32
-3

I used lodash.

import _ from 'lodash'
class Car {
 clone() { return _.cloneDeep(this); }
}
answered Apr 6, 2022 at 21:11
-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()
answered Apr 21, 2022 at 16:53
20
  • "a shallow copy [...] is actually a huge issue because javascript garbage collection [...] means a potentially critical memory leak" - nope, absolutely not. Commented Apr 21, 2022 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. Commented Apr 21, 2022 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. Commented Apr 21, 2022 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. Commented Apr 21, 2022 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. Commented Apr 21, 2022 at 18:24
-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" };
answered May 8, 2020 at 20:07
1
  • 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. Commented Nov 25, 2021 at 13:29

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.