Closed glenjamin closed 7 years ago
class Derived extends Base {
constructor(...args) {
super(...args);
this.init();
}
}
Using a rest arg, the subclass can and should easily pass along their constructor arguments - there's no need for an "init" pattern at all.
The best way to think about this is to think about how you'd write this code without the new class fields feature. Here's a "desugaring" of what you have now:
class Base {
constructor(lots, of, args, that, are, a pain, to, repeat, when, defining, children) {
this.y = 7;
/**
Do work using all those constructor args
**/
this.init();
}
init() {
// extension point for subclasses so they don't have to pass along children
}
}
class Derived extends Base {
constructor(...args) {
super(...args);
this.z = 4;
}
init() {
alert(this.z * this.y);
}
}
var q = new Derived();
When you omit a constructor on a child class, you're actually just asking the JS engine to create an implicit constructor for you that looks like:
constructor(...args) {
super(...args);
}
So when you visualize this in terms of this desugaring and try to do the same thing with that code, you're left asking for a way for the base class to run code after the last child class's constructor runs -- which JS doesn't have any language-level support for.
Hope that makes sense!
Hope that makes sense!
It does, I understand how the execution model of this feature leads to the scenario I've described.
The disconnect is because the new syntax is quite declarative, but ultimately has an imperative execution model. A slightly less contrived example might be having a derived class override some property of the parent.
class Base {
x = 1;
y = 2 * x;
}
class Derived extends Base {
x = 2;
z = y + 3;
}
The result of the above can be figured out given knowledge of the underlying execution model, but I think it might appear non-obvious to a newcomer looking at this code.
I'm wondering whether anything can be done to produce a warning or an error in such scenarios - or make their construction less likely.
I'm not sure how we can prevent people from not understanding the language. If y
is supposed to be a live value based on the momentary value of x
, then it needs to be a function or a getter.
Well for example if the properties were resolved statically into a single set of keys before evaluation - the above example would produce a different answer.
Likewise I sometimes forget that I can't use values from property initialisers in the constructor.
Both of these "gotchas" (for want of a better term) arise from the combination of declarative syntax and imperative execution model.
This is currently just sugar for assigning properties at the end of a constructor. Perhaps it could be more?
@glenjamin: Unfortunately there are too many dynamic entry-points into classes in JS to make any reasonable static assumptions about fields.
The most glaring example would be a case that takes advantage of how extends
clauses are dynamic in JS:
class C extends (Math.random() > .5 ? generateClass() : generateOtherClass()) {
}
I understand why the current order is the way that it is, but I have a scenario I thought would be supported but currently is not.
Also, is there a people-targetted document which outlines the order? I only established the order from a combination of squinting at the spec and trying examples.
Consider the following example
The motivation was to avoid having children repeat the constructor signature - and potential errors caused by not passing along arguments correctly. When I attempted to use this approach I hit a snag, the execution order is:
Is there any way to place an init routine somewhere after all class properties have been evaluated?