25

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?

Schlaus
  • 18,144
  • 10
  • 36
  • 64
  • 6
    What `this` refers to in `init` only depends on how the function is called. If you call it with `Parent.init()` then it refers to `Parent`. If you call it with `Child1.init()` then it refers to `Child1`. That has nothing to do with ES6, it's how JavaScript always worked (and ES6 does not change that). – Felix Kling Sep 09 '15 at 16:52
  • There is something about extending here in "The extends clause" : http://www.2ality.com/2015/02/es6-classes-final.html – angellica.araujo Sep 09 '15 at 16:56
  • possible duplicate of [ES6 - Call static method within a class](http://stackoverflow.com/q/31116300/1048572) – Bergi Sep 09 '15 at 20:59

2 Answers2

19

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.)

Community
  • 1
  • 1
T.J. Crowder
  • 1,031,962
  • 187
  • 1,923
  • 1,875
  • Thanks! I didn't know that class syntax was sugar in a permanent way, or in other words, I knew how it works with the transpilers, but I wasn't sure it would still work like that when implemented natively in browsers. I feared this behavior might be just an artifact of the ES version conversion, which would go away later, breaking my code. It's good to know that wont happen! – Schlaus Sep 09 '15 at 18:23
  • Inheritance is a bit more than sugar though. Especially we had no constructor functions inheriting from other constructor functions, which allows us to use `super` even in static methods – Bergi Sep 09 '15 at 21:01
  • @Bergi: But again, that's just syntactic sugar. If I have `Child extends Parent`, then in a `Child` static method I use `super.staticMethod()`, the unsugared version is simply `Parent.staticMethod()`. It's true that ES6 has `Parent` in the prototype chain of `Child` (e.g., `Child -> Parent -> Function.prototype -> Object.prototype`), but we could do that with `setPrototypeOf`. It's `setPrototypeOf` rather than `class` that's the advance there. Don't get me wrong, I'm a big fan of the sugar, but for now I think that's all it is, isn't it? (Won't be very surprised if you catch me out here.) – T.J. Crowder Sep 10 '15 at 00:56
  • 1
    Yeah, I guess most of `super` is desugarable, even the [[HomeObject]]. The only ES6 class thing that is not would be [newtarget](http://stackoverflow.com/q/32450516/1048572) then… – Bergi Sep 10 '15 at 10:48
  • @Bergi: Ah, you've put your finger on it: The way the new syntax lets us extend builtins like `Array` is definitely not just sugar! (Even `new.target` is basically accessible in the "old" way as `this.constructor` -- assuming someone hasn't messed up the `constructor` property somewhere along the way -- but the wiring *is* slightly different, and better, with the new syntax. Knew I could count on you.) – T.J. Crowder Sep 10 '15 at 11:00
15

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'
Kilian Obermeier
  • 6,678
  • 4
  • 38
  • 50