How to clone a javascript ES6 class instance

59,462

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

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

Solution 3

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

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

Solution 5

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)

Share:
59,462
Tom
Author by

Tom

C++ dev, some Java on Android & GAE, now js because - like it or not - it is the universal language.

Updated on January 08, 2022

Comments

  • Tom
    Tom over 2 years

    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.

  • barbatus
    barbatus about 7 years
    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.
  • Bergi
    Bergi almost 7 years
    @barbatus That uses the wrong prototype though, Blah.prototype != instanceOfBlah. You should use Object.getPrototypeOf(instanceOfBlah)
  • barbatus
    barbatus over 6 years
    @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.
  • Bergi
    Bergi over 6 years
    @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.
  • barbatus
    barbatus over 6 years
    @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.
  • Bergi
    Bergi over 6 years
    @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…
  • barbatus
    barbatus over 6 years
    @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)?
  • Bergi
    Bergi over 6 years
    @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.
  • Mr. Lavalamp
    Mr. Lavalamp over 6 years
    This won't copy static methods because they are not actually enumerable own properties.
  • flori
    flori over 6 years
    @Mr.Lavalamp and how can you copy (also) the static methods?
  • Toothbrush
    Toothbrush about 6 years
    How about Object.assign(Object.create(Object.getPrototypeOf(instanceOf‌​Blah)), instanceOfBlah)? It doesn't do a deep clone, but it works great for most purposes.
  • Vahid
    Vahid over 5 years
    this will destroy the arrays! It'll convert all arrays to objects with "0","1",... keys.
  • Kesha Antonov
    Kesha Antonov over 5 years
    @vmoh_ir how to deal with this?
  • Vahid
    Vahid over 5 years
    @KeshaAntonov You may be able to find a solution with typeof and Array methods. I myself prefered to clone all properties manually.
  • jduhls
    jduhls over 5 years
    Do not expect it to clone properties that are themselves objects: jsbin.com/qeziwetexu/edit?js,console
  • pery mimon
    pery mimon over 5 years
    Static method not need to be cloned! they part of the Class not the instance
  • Heimdell
    Heimdell about 5 years
    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.
  • flori
    flori about 5 years
    @Heimdell Actually it does already do Object.create(orig.getPrototypeOf()) which is equal to your Object.create(orig.__proto__)
  • trincot
    trincot over 4 years
    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.
  • flori
    flori over 4 years
    @trincot Thanks, I added notes regarding your mentioned limitations. Symbols seems to work fine(?) Shallow cloning is, as I would say, as expected(?)
  • Bergi
    Bergi over 4 years
    It would be better if you'd edit your previous answer instead of deleting and reposting it.
  • Bergi
    Bergi over 4 years
    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?
  • Cassio Seffrin
    Cassio Seffrin over 4 years
    @Bergi thks for your contribution, I was editing the my answer right now, I added your point to copy the prototypes!
  • Cassio Seffrin
    Cassio Seffrin over 4 years
    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
    Cassio Seffrin over 4 years
    They are the same of Object.assign({},original), but with a shorter sintax, I think now all the options were exposed in the answer.
  • Bergi
    Bergi over 4 years
    Yes, and just like Object.assign({},original), it does not work.
  • Cassio Seffrin
    Cassio Seffrin over 4 years
    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
  • Admin
    Admin over 3 years
    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
  • VLAZ
    VLAZ over 2 years
    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
    VLAZ over 2 years
    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
    VLAZ over 2 years
    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
    VLAZ over 2 years
    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
    VLAZ over 2 years
    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.
  • T.Todua
    T.Todua over 2 years
    @Bergi can you please provide full answer how this answer should have been to be correct?
  • Bergi
    Bergi over 2 years
    @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.
  • T.Todua
    T.Todua over 2 years
    @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.
  • Bergi
    Bergi over 2 years
    @T.Todua None of the solutions here do a deep clone
  • Thomas
    Thomas over 2 years
    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
  • Danny
    Danny over 2 years
    ok I'm using like this and I guess it's enough in my case
  • Shaun Scovil
    Shaun Scovil about 2 years
    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
  • lawrence-witt
    lawrence-witt almost 2 years
    TypeError: this.color.clone is not a function