How to clone a javascript ES6 class instance

2020-02-16 08:33发布

问题:

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.

回答1:

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 to set 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, once there will be a native and clean clone functionality.



回答2:

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.



回答3:

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.