1

I'd like to use ES6 public class fields:

class Superclass {
    constructor() {
        // would like to write modular code that applies to all
        // subclasses here, or similarly somewhere in Superclass
        
        this.example++;  // does NOT WORK (not intialized)
        //e.g. doStuffWith(this.fieldTemplates)
    }
}

class Subclass extends Superclass {
    example = 0
    static fieldTemplates = [
        Foo, 
        function() {this.example++}, 
        etc
    ]
}

Problem:

ES6 public fields are NOT initialized before the constructors, only before the current constructor. For example, when calling super(), any child field will not yet have been defined, like this.example will not yet exist. Static fields will have already been defined. So for example if one were to execute the code function(){this.example++} with .bind as appropriate, called from the superclass constructor, it would fail.

Workaround:

One workaround would be to put all initialization logic after all ES6 public classes have been properly initialized. For example:

class Subclass extends Superclass {
    example = 0
    lateConstructor = (function(){
        this.example++; // works fine
    }).bind(this)()
}

What's the solution?

However, this would involve rewriting every single class. I would like something like this by just defining it in the Superclass.constructor, something magic like Object.defineProperty(this, 'lateConstructor', {some magic}) (Object.defineProperty is allegedly internally how es6 static fields are defined, but I see no such explanation how to achieve this programatically in say the mozilla docs; after using Object.getOwnPropertyDescriptor to inspect my above immediately-.binded-and-evaluated cludge I'm inclined to believe there is no way to define a property descriptor as a thunk; the definition is probably executed after returning from super(), that is probably immediately evaluated and assigned to the class like let exampleValue = eval(...); Object.defineProperty(..{value:exampleValue})). Alternatively I could do something horrible like do setTimeout(this.lateConstructor,0) in the Superclass.constructor but that would break many things and not compose well.

I could perhaps try to just use a hierarchy of Objects everywhere instead, but is there some way to implement some global logic for all subclasses in the parent class? Besides making everything lazy with getters? Thanks for any insight.

References:

Run additional action after constructor -- (problems: this requires wrapping all subclasses)

ninjagecko
  • 88,546
  • 24
  • 137
  • 145
  • 1
    No, this is not possible. But it's smelling like a design problem if a constructor is trying to do something that requires the subclass fields. See also [calling overridden methods from the constructor](https://stackoverflow.com/q/47820030/1048572) – Bergi Dec 19 '20 at 21:28
  • Can you give a better (and more complete, including `doStuffWith`) example? What you currently have would probably be solved by doing `example = 1` and not incrementing it in the constructor… – Bergi Dec 19 '20 at 21:29
  • "*magic like `Object.defineProperty(this, 'lateConstructor', {some magic})` is allegedly how es6 fields are defined internally*" - no, where did you get that? As you said, fields defined in a subclass are initialised immediately after `super()`. – Bergi Dec 19 '20 at 21:39
  • @Bergi: I meant Object.defineProperty itself is, allegedly; https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes/Public_class_fields says "*Public static fields are declared using the static keyword. They are added to the class constructor at the time of class evaluation using Object.defineProperty(). They are accessed again from the class constructor.*"; I clarified my understanding of that in the next paragraph (in a later edit at some point); I guess that only applies to static ones. – ninjagecko Dec 19 '20 at 21:43
  • Ah, I see. That only refers to the semantics of not triggering setters ("as if the properties were created by `defineProperty`, not through assignment"), it's nothing to do with thunks. Static properties get created when the class is defined, instance properties get created when the instance is constructed. The sentence "*They are accessed again from the class constructor.*" is pretty weird. It seems to mean that you can access static properties as properties of the constructor function object. – Bergi Dec 19 '20 at 21:48
  • Related, likely dupe: [Access JavaScript class property in parent class](/q/55479511/4642212). – Sebastian Simon Nov 10 '21 at 08:49

2 Answers2

1

Can I create a thunk to run after the constructor?

No, that is not possible.

How to run code after class fields are initialized, in a sane way?

Put the code in the constructor of the class that defines those fields.

Is there some way to implement some global logic for all subclasses in the parent class?

Yes: define a method. The subclass can call it from its constructor.

Bergi
  • 630,263
  • 148
  • 957
  • 1,375
1

Just thought of a workaround (that is hierarchically composable). To answer my own question, in a somewhat unfulfilling way (people should feel free to post better solutions):

// The following illustrates a way to ensure all public class fields have been defined and initialized
// prior to running 'constructor' code. This is achieved by never calling new directly, but instead just
// running Someclass.make(...). All constructor code is instead written in an init(...) function.

class Superclass {
    init(opts) {  // 'constructor'
        this.toRun();  // custom constructor logic example
    }
    
    static make() {  // the magic that makes everything work
        var R = new this();
        R.init(...arguments);
        return R;
    }
}
    
class Subclass extends Superclass {
    subclassValue = 0  // custom public class field example

    init(toAdd, opts) {  // 'constructor'
        // custom constructor logic example
        this.subclassValue += toAdd;  // may use THIS before super.init
        
        super.init(opts);

        // may do stuff afterwards
    }

    toRun() {          // custom public class method example
        console.log('.subclassValue = ', this.subclassValue);
    }
}

Demo:

> var obj = Subclass.make(1, {});
.subclassValue =  1

> console.log(obj);
Subclass {
    subclassValue: 1
    __proto__: Superclass
}
ninjagecko
  • 88,546
  • 24
  • 137
  • 145
  • Yes, this is not just a workaround, this is the proper solution! I completely forgot about this approach since the question was so focused on the constructors. In any case where stuff should start running (potentially with side effects), that should happen in a method to be invoked separately after the pure instance initialisation by the constructor. A static factory method can ease the syntactic overhead for the caller. – Bergi Dec 19 '20 at 22:09