How do I clone a JavaScript class instance?
I tried the normal jQuery extend, but that just returns a vanilla object. I have looked through many other answers on sta
How do I clone a JavaScript class instance?
It's hardly possible if the instance was created with heavy use of closures in the constructor function. We may never now which internal values were set, and how to reproduce such a setup. Therefore, the easiest way would be if every class offered a clone
function
which knows what to do.
normal jQuery extend just returns a vanilla object
Not necessarily, it returns what you passed in. Use
var clone = $.extend(true, Object.create(Object.getPrototypeOf(child)), child);
instead and your instanceof
usage will work fine. Note that the true
signifies a "deep" copy which may or may not be what you want. Also, $.extend
will happily copy enumerable inherited properties as well, so you might need to use a more sophisticated extend
function.
Or without jQuery at all, and only copying own, enumerable properties and only using a shallow copy:
var clone = Object.assign(Object.create(Object.getPrototypeOf(child)), child);
But again, not all objects will be clonable in this way, see my first point above.
You should try something like this:
function clone_object(o){
var n=Object.create(
Object.getPrototypeOf(o),
Object.getOwnPropertyNames(o).reduce(
function(prev,cur){
prev[cur]=Object.getOwnPropertyDescriptor(o,cur);
return prev;
},
{}
)
);
if(!Object.isExtensible(o)){Object.preventExtensions(n);}
if(Object.isSealed(o)){Object.seal(n);}
if(Object.isFrozen(o)){Object.freeze(n);}
return n;
}
Narrative:
Object.create
from a prototype and a properties object.Object.getPrototypeOf
.getOwnPropertyNames
), and retrieve the property descriptor for each using getOwnPropertyDescriptor
.This will not deep-clone properties whose values are themselves objects. That's left as an exercise to the reader...YMMV.
This will create a copy of an object with the same prototype (class) and same own properties (including enumerability/writability/getters/setters etc):
function clone(obj) {
return Object.create(
Object.getPrototypeOf(obj),
Object.getOwnPropertyDescriptors(obj)
);
}
(see here)
It doesn't necessarily work well for builtin objects. For example Array.isArray(clone([]))
is false
, and clone(function () {})()
says it is not a function, but for user created objects (either class instances or object literals) it works fine.
To do a deep clone, you will have to loop over the property descriptors and clone the values recursively:
function deepClone(obj) {
if (obj === null || typeof obj !== "object")
return obj
var props = Object.getOwnPropertyDescriptors(obj)
for (var prop in props) {
props[prop].value = deepClone(props[prop].value)
}
return Object.create(
Object.getPrototypeOf(obj),
props
)
}
I think that not necessarily needs to work with classes(or functions instances), you could extend prototype to apply OOP. My suggestion is that you extend prototype instead of create classes. jQuery is for DOM but not for advance object treatment so while $.extend could be helpful in some cases for complex stuff there are more advanced libraries.
You could use libraries like CloneJS to easily work with extendable objects: https://npmjs.org/package/clonejs
Just include this script: http://quadroid.github.io/clonejs/cdn/clone.min.js
And try their own example:
/// Forget about classes.
// Instead of creating class (function), create prototype (object):
var $duck = $object.clone({
name: 'Unnamed',
quack: function(){
console.log( this.name +' Duck: Quack-quack!');
}
});
$duck.quack();//Unnamed Duck: Quack-quack!
/// Inheritance is simple:
var $talkingDuck = $duck.clone({
quack: function(){
this.applySuper('quack');
console.log('My name is '+ this.name +'!');
}
});
/// Forget about the `new` operator, use .create() method instead:
var donald = $talkingDuck.create({name: 'Donald'});
donald.quack();// Donald Duck: Quack-quack! My name is Donald!
/// Forget about the `instanceof` operator, use JS native
// .isPrototypeOf() method instead:
$duck.isPrototypeOf(donald);// true
Also I think that Backbone.js applies the extension of prototype instead of creation of classes. They use _.extend
Some more references: http://www.2ality.com/2011/11/javascript-classes.html http://underscorejs.org/#extend
I'd be interested to see someone benchmark this method against other approaches to cloning class instances in JavaScript that use native JavaScript.
// Defining a class
function MyClass(args) {
this.foo = "bar";
}
// Avoid duplicate instances of class methods in RAM by adding them to the class prototype like this
MyClass.prototype.method = method;
MyClass.prototype.clone = clone;
// Define what your methods do
function method() {
console.log(this.foo);
}
function clone() {
var classScope = this;
// Get the prototype of your class. This will create an object that has one key for every method in your class. I'm not sure if this will go up the prototype chain if you have subclasses. Someone ought to edit this answer to clarify that.
var miniMe = Object.getPrototypeOf(this);
// Iterate the properties of your class--all the internal key-value pairs that do get duplicated in RAM each time you instantiate the class.
Object.keys(this).forEach(iterate);
function iterate(key, index, list) {
// Add each property to your clone
miniMe[key] = classScope[key];
}
// Return the clone
return miniMe;
}
// Instantiate your class
var me = new MyClass();
// Clone it
var miniMe = me.clone();
// Run some tests
Object.keys(Object.getPrototypeOf(me)).forEach(iterate);
Object.keys(me).forEach(iterate);
function iterate(property, index, list) {
if (!miniMe.hasOwnProperty(property))
throw new Error("miniMe does not have property " + property);
}
// Change the value of miniMe.foo and make sure it didn't impact the value of me.foo
miniMe.foo = "baz";
if (me.foo === miniMe.foo)
throw new Error("me.foo should not equal miniMe.foo, because we changed its value");
Edited: When you know what kind of object you want to clone you can follow my example:
In your example you can simply do something like this:
var child = new Child('Billy');
var clone = new Child();
for (var prop in child) {
clone[prop] = child[prop];
}
I have updated your jsFiddle