Reputation: 30576
The traditional way of extending classes in JS is something like this:
// define constructor function
function Fuu(){}
// extend prototype and override prototype's constructor
Fuu.prototype = Object.create(OtherClass.prototype, {
constructor: {
value: Fuu,
enumerable: false,
writable: true,
configurable: true
}
});
Then you add the methods you want to the prototype
Fuu.prototype.method = function() {}
And just like that you have a function extending another. A nice example of inheritance in JS!
My question is how to extend when the sub class already has a prototype with methods and properties. I could try to copy the the methods of the old prototype to the new one using a for in
loop but the methods are non enumerable(class is created with a transpiler) and doing something with getOwnPropertyNames
doesn't seem right. Any suggestion? can I do something like keeping the prototype and adding a prototype to the prototype?
Edit: example
class Fuu {
someMethod(){} // non enumerable method in Fuu's prototype
}
// My first option: (extending this way `someMethod` is lost)
Fuu.protoype = Object.create(HTMLElement.prototype, {//...same as before})
// Option 2: copy methods from old to new prototype
// Option 3: prototype of prototype?
// Fuu.prototype.prototype = Object.create(HTMLElement.prototype, {...})
Upvotes: 1
Views: 1870
Reputation: 1251
I think the method you have suggested is probably the bet way to go. Is there a reason why you think it is wrong?
var old = Fuu.prototype;
Fuu.prototype = Object.create(OtherClass.prototype, {
constructor: {
value: Fuu,
enumerable: false,
writable: true,
configurable: true
}
});
var names = Object.getOwnPropertyNames(old);
for (var i = 0; i < names.length; i++) {
var name = names[i];
Fuu.prototype[name] = old[name];
}
The only thing I'd be concerned about is your constructor
method being overridden by the old version, and for your old prototype's prototype chain being lost; however you can do things to fix this.
Upvotes: 1
Reputation: 288010
You want something like
┌──> Fuu.prototype
instances ──┤
└──> OtherClass.prototype
But that's not possible, because objects only have one [[Prototype]].
Therefore, you must achieve one of these:
instances ───> Fuu.prototype ───> OtherClass.prototype
instances ───> OtherClass.prototype ───> Fuu.prototype
So you must set the [[Prototype]] of one of those to be the other one. I will assume the first possibility.
There are two main ways to set the [[Prototype]]:
Object.create
, when creating the object
The problem is that both Fuu.prototype
and OtherClass.prototype
have been created already.
However, you can create a new object with the right [[Prototype]] and assign the properties of the old one.
Since there may be non-enumerable properties, you must use getOwnPropertyNames
. Using defineProperty
and getOwnPropertyDescriptor
may also be a good idea, in case there are getters or setters.
var old = Fuu.prototype,
props = Object.getOwnPropertyNames(old);
Fuu.prototype = Object.create(OtherClass.prototype);
for(var i=0; i<props.length; ++i)
Object.defineProperty(
Fuu.prototype,
props[i],
Object.getOwnPropertyDescriptor(old, props[i])
);
setPrototypeOf
or __proto__
(ES6), once the object has been created:
Object.setPrototypeOf(Fuu.prototype, OtherClass.prototype);
Fuu.prototype.__proto__ = OtherClass.prototype;
However, be aware that
Mutating the [[Prototype]] of an object is, by the nature of how modern JavaScript engines optimize property accesses, a very slow operation, in every browser and JavaScript engine. The effects on performance of mutating prototypes [...] may extend to any code that has access to any object whose [[Prototype]] has been mutated. If you care about performance you should avoid mutating the [[Prototype]] of an object.
Upvotes: 4