olanod
olanod

Reputation: 30576

Extend already defined class in JavaScript

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

Answers (2)

Wio
Wio

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

Oriol
Oriol

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

Related Questions