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.
15 Answers 15
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.
-
3This won't copy static methods because they are not actually enumerable own properties.Mr. Lavalamp– Mr. Lavalamp2017年12月22日 06:26:51 +00:00Commented Dec 22, 2017 at 6:26
-
7@Mr.Lavalamp and how can you copy (also) the static methods?flori– flori2017年12月23日 12:53:22 +00:00Commented 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.Vahid– Vahid2018年10月21日 18:24:27 +00:00Commented Oct 21, 2018 at 18:24
-
5Do not expect it to clone properties that are themselves objects: jsbin.com/qeziwetexu/edit?js,consolejduhls– jduhls2018年11月13日 20:12:31 +00:00Commented Nov 13, 2018 at 20:12
-
5Static method not need to be cloned! they part of the Class not the instancepery mimon– pery mimon2019年01月10日 09:33:17 +00:00Commented Jan 10, 2019 at 9:33
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
-
8This 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– VLAZ2021年11月25日 13:56:40 +00:00Commented Nov 25, 2021 at 13:56 -
6This 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/def81066dc59e6ff5084a499d9855253Shaun Scovil– Shaun Scovil2022年02月22日 21:02:30 +00:00Commented 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 defaultSirDorius– SirDorius2023年09月20日 10:37:50 +00:00Commented Sep 20, 2023 at 10:37
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.
-
3Since
Object.create
creates new object with specified prototype, why not then justconst clone = Object.assign(Object.create(instanceOfBlah), instanceOfBlah)
. Also class methods will be copied as well.barbatus– barbatus2017年02月19日 11:21:49 +00:00Commented Feb 19, 2017 at 11:21 -
3@barbatus That uses the wrong prototype though,
Blah.prototype != instanceOfBlah
. You should useObject.getPrototypeOf(instanceOfBlah)
Bergi– Bergi2017年07月15日 01:56:23 +00:00Commented 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.barbatus– barbatus2017年09月02日 07:52:22 +00:00Commented 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.Bergi– Bergi2017年09月02日 08:55:48 +00:00Commented 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.barbatus– barbatus2017年09月02日 08:59:59 +00:00Commented Sep 2, 2017 at 8:59
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.
-
3When assigning to
{}
, the clone won't inherit any of the prototype methods of the original.clone.logFullName()
will not work at all. TheObject.assign( Object.create(Object.getPrototypeOf(eOriginal)), eOriginal)
you had before was fine, why did you change that?Bergi– Bergi2019年12月31日 13:32:12 +00:00Commented 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!Cassio Seffrin– Cassio Seffrin2019年12月31日 13:43:01 +00:00Commented Dec 31, 2019 at 13:43
-
2I 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– Cassio Seffrin2019年12月31日 13:50:02 +00:00Commented Dec 31, 2019 at 13:50
-
1Yes, and just like
Object.assign({},original)
, it does not work.Bergi– Bergi2019年12月31日 15:36:11 +00:00Commented Dec 31, 2019 at 15:36 -
2sometimes the simpler approach is all we need. If you don't need Prototypes and complex objects may just "clone = { ...original }" could solve the problemCassio Seffrin– Cassio Seffrin2019年12月31日 19:39:41 +00:00Commented Dec 31, 2019 at 19:39
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)
-
2This 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– VLAZ2021年11月25日 13:36:49 +00:00Commented Nov 25, 2021 at 13:36
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.
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
);
Object.getOwnPropertyNames
, it will also add non-enumerable properties.
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.
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
}
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)
-
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, considerconstructor(a, b) { this.c = a + b }
which normally expects numbers but gets an instance of itself fora
andundefined
forb
.VLAZ– VLAZ2021年11月25日 13:52:56 +00:00Commented Nov 25, 2021 at 13:52
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);
-
1Two issues here - 1. this loses the class information -
output instanceof A
isfalse
. 2. The cloning is only one level up the prototype chain, if there is aclass B extends A { b() { return 2; }}
andclass C extends B { c() { return 3; }}
then "cloning" an instance ofC
ends up only copyingb()
andc()
but not the properties ofA
(y
). The propertyx
is going to be copied only because it's set in the constructor directly on the instance.VLAZ– VLAZ2021年11月25日 13:46:22 +00:00Commented Nov 25, 2021 at 13:46
Is it not enough to do like this ?
Object.assign(new ClassName(), obj)
-
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 pitfallsThomas– Thomas2022年01月07日 11:45:36 +00:00Commented Jan 7, 2022 at 11:45 -
ok I'm using like this and I guess it's enough in my caseDanny– Danny2022年01月17日 15:32:43 +00:00Commented Jan 17, 2022 at 15:32
I used lodash.
import _ from 'lodash'
class Car {
clone() { return _.cloneDeep(this); }
}
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– Bergi2022年04月21日 17:22:00 +00:00Commented 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.Kris Driver– Kris Driver2022年04月21日 17:41:07 +00:00Commented Apr 21, 2022 at 17:41
-
Nope. There is no reference from
newObj
toobj
. Sure, both contain a reference to the same array, but that does not prevent garbage collection ofobj
in any way.Bergi– Bergi2022年04月21日 18:12:25 +00:00Commented 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.Kris Driver– Kris Driver2022年04月21日 18:17:56 +00:00Commented 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.Bergi– Bergi2022年04月21日 18:24:14 +00:00Commented Apr 21, 2022 at 18:24
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" };
-
This loses the tprototype information, which includes the class. If
obj = new A()
, thenclone instanceof A
isfalse
. Which also means that methods are lost, as would any other non-enumerable properties the instance might have.VLAZ– VLAZ2021年11月25日 13:29:52 +00:00Commented Nov 25, 2021 at 13:29
Explore related questions
See similar questions with these tags.
Object
s used as data holders. This one is about ES6class
es and the problem to not lose the class type information. It needs a different solution.