How to clone a javascript ES6 class instance
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)
Tom
C++ dev, some Java on Android & GAE, now js because - like it or not - it is the universal language.
Updated on January 08, 2022Comments
-
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 about 7 yearsSince
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. -
Bergi almost 7 years@barbatus That uses the wrong prototype though,
Blah.prototype != instanceOfBlah
. You should useObject.getPrototypeOf(instanceOfBlah)
-
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 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 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 over 6 years@barbatus But never for an object that was instantiated with
new
from a normalclass
. And even then, you would want the clone to have the same prototype… -
barbatus over 6 years@Bergi how are then prototypes of objects created by
Object.create(instance)
andObject.create(Object.getPrototypeOf(instance))
different (given that it's for cloning)? -
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 over 6 yearsThis won't copy static methods because they are not actually enumerable own properties.
-
flori over 6 years@Mr.Lavalamp and how can you copy (also) the static methods?
-
Toothbrush about 6 yearsHow about
Object.assign(Object.create(Object.getPrototypeOf(instanceOfBlah)), instanceOfBlah)
? It doesn't do a deep clone, but it works great for most purposes. -
Vahid over 5 yearsthis will destroy the arrays! It'll convert all arrays to objects with "0","1",... keys.
-
Kesha Antonov over 5 years@vmoh_ir how to deal with this?
-
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 over 5 yearsDo not expect it to clone properties that are themselves objects: jsbin.com/qeziwetexu/edit?js,console
-
pery mimon over 5 yearsStatic method not need to be cloned! they part of the Class not the instance
-
Heimdell about 5 yearsThis thing will make
orig
a prototype of newly created object. Consider doingObject.create(orig.__proto__)
instead ofObject.create(orig)
, or if you repeat this 100 times, you will end with 100+-long prototype chain. -
flori about 5 years@Heimdell Actually it does already do
Object.create(orig.getPrototypeOf())
which is equal to yourObject.create(orig.__proto__)
-
trincot over 4 yearsDoes 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
, ... Iforig = [1,2,3]
, thenclone.length === 0
. Not running the constructor is problematic in many cases. -
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 over 4 yearsIt would be better if you'd edit your previous answer instead of deleting and reposting it.
-
Bergi over 4 yearsWhen 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? -
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 over 4 yearsI 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 over 4 yearsThey are the same of Object.assign({},original), but with a shorter sintax, I think now all the options were exposed in the answer.
-
Bergi over 4 yearsYes, and just like
Object.assign({},original)
, it does not work. -
Cassio Seffrin over 4 yearssometimes 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 over 3 yearsIMO 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 over 2 yearsThis 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 over 2 yearsThis 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 over 2 yearsTwo 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 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, considerconstructor(a, b) { this.c = a + b }
which normally expects numbers but gets an instance of itself fora
andundefined
forb
. -
VLAZ over 2 yearsThis 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 over 2 years@Bergi can you please provide full answer how this answer should have been to be correct?
-
Bergi over 2 years@T.Todua Just what Toothbrush suggested, or the accepted answer from flori. You can exchange
Object.getPrototypeOf(instanceOfBlah)
forBlah.prototype
if you know the class of the object to be cloned. -
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 over 2 years@T.Todua None of the solutions here do a deep clone
-
Thomas over 2 yearsIt 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 over 2 yearsok I'm using like this and I guess it's enough in my case
-
Shaun Scovil about 2 yearsThis 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 almost 2 yearsTypeError: this.color.clone is not a function