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
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.
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');
let clone = Object.assign({},original); //object.assing() method
let cloneWithPrototype Object.create(Object.getPrototypeOf(original)), original) // the clone will inherit the prototype methods of the original.
let clone2 = { ...original }; // the same of object assign but shorter sintax using "spread operator"
clone.firstName = 'John';
clone.address.street = 'Street B, 99'; //will not be cloned
Results:
original.logFullName():
result: Cassio Seffrin
clone.logFullName():
result: John 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.
clone.logFullName()
will not work.
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.
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.
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)
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" };
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