Calling an overridden static method from parent

2020-07-01 02:19发布

问题:

First, some code to set the stage:

var instances = [];

class Parent {
    static doImportantStuff() {
        console.log( 'Parent doing important stuff' );
        return false;
    }

    static init() {
        if ( this.doImportantStuff() )
            instances.push( new this() );
    }
}

class Child1 extends Parent {
    static doImportantStuff() {
        console.log( 'Child 1 doing important stuff' );
        if (someCondition) return true;
        return false;
    }
}

class Child2 extends Parent {
    static doImportantStuff() {
        console.log( 'Child 2 doing important stuff' );
        if (someOtherCondition) return true;
        return false;
    }
}

The idea is that there's a class Parent that several Child classes extend. Initialization of the Child classes is mostly the same, but each has their own implementation of doImportantStuff(), the return value of which indicated whether that particular Child should be instantiated.

So far this has worked in each transpiler I've tried, because this in the Parent.init() function refers to the constructor of the Child class. However I haven't found any documentation saying one way or the other about referring to a static method overridden by a child class, so the question is, can I rely on this always being so? Or, is there some other way to do this?

回答1:

However I haven't found any documentation saying one way or the other about referring to a static method overridden by a child class, so the question is, can I rely on this always being so?

It's the standard function-call-via-object-property mechanism. When you do:

Child1.doImportantStuff();

...unless doImportantStuff is an arrow function (it isn't) or a bound function (it isn't), then during the call, this is set to Child1. Exactly like:

var obj = {
   foo: function() {
        console.log(this === obj);
   }
};
obj.foo();   // "true"

So yes, you can rely on that. (And I understand why you asked, it does seem a bit odd unless you work it through.)

Of course, it won't work from within the code of a non-static function, because this will refer to the instance, not the constructor function. If you needed it there, you could use this.constructor.doImportantStuff unless someone's messed up the constructor property. (People always used to mess it up; with the new syntax automating it, hopefully that will happen less although it's rare you really need it...)


For these kinds of questions, it's frequently useful to remember that the new class syntax is almost just syntactic sugar for the old verbose way we did it (if we were really thorough). It's really good sugar, but that's nearly all it is (and that's a Good Thing™). static methods are set as properties of the constructor function, non-static methods are set up as properties of the object on the constructor's prototype property. (I think the only non-sugar aspect of it is, as Bergi points out, that the new syntax lets us extend builtins like Array which it wasn't possible to do before. Part of making that possible relates to how and when this gets set up [you can't access it prior to the super() call in your subclass constructor], which relates to new.target, which Bergi discusses here. In ES7, it may go further beyond sugar with privacy stuff.)



回答2:

If you don't want to call

Child1.doImportantStuff();

in Parent, but rather dynamically invoke the overridden static method, you can do:

this.constructor.doImportantStuff();

This also works for setters and in the constructor of the parent class:

class Parent {
    static get foo() {
        // Throw an error to indicate that this is an abstract method.
        throw new TypeError('This method should be overridden by inheriting classes.');
    }

    constructor() {
        console.log(this.constructor.foo);
    }

    logFoo() {
        console.log(this.constructor.foo);
    }
}

class Child extends Parent {
    static get foo() {
        return 'yay';
    }
}

const child = new Child(); // Prints 'yay'
child.logFoo(); // Prints 'yay'