OK, I've read it all, thanks for sharing, but it's not super related ...
- the sub-class method accesses a property that doesn't even exist on the super so that's an expected shenanigan
- the sub-class constructor explicitly set helper after, while here everything is implicit and non fine-tunable from the author of the class which misleads results (WYSI-NOT-WYG)
- there's no
abstract
in JS, meaning in JS nobody can really signal a class is meant to be subclassed or not ... but even ifabstract
was a thing, the issue presented here has nothing strictly to do with methods (I am considering accessors methods too, even if as workaround these work) - the key to discuss, which I did in my medium's post, is the
final
keyword ... allow me to expand
class Point {
x = 0; // this is final !!!
y = 0; // this is not
xRelated; // dummy example
constructor() {
// x is final because it's used in the constructor
// y is not final because it's *not* used in here
// dummy example that uses the x for whatever reason
this.xRelated = `point x: ${this.x}`;
}
getCoords() {
return [this.x, this.y];
}
}
If we had a final
syntax/keyword, we can signal which properties should never be overridden and which are safe to assume will always work as expected or can be accessed without issues ... let me get to the point:
class ThePoint extends Point {
x = 1; // this *should* throw
y = 1; // this is fine
constructor() {
super();
console.log(this.xRelated); // "point x: 0"
console.log(this.getCoords()); // [1, 1]
}
}
In few words, the x
used in the constructor has no way to signal that it should never be overridden at the sub class definition, while the Java example you linked has this ability via private final FooBar fooBar;
Now, assuming we have no way to fix this, can we at least consider bringing final
at least for classes fields?
So now we talk, or better, now we have a technical reason to justify the current approach, hence something to discuss ... and yet your argument is very weak, because if function () { return {} }
every super field is fully ignored, while sub-class fields will be attached:
class Root {
kind = 'wood';
constructor() {
// this is the B method from Sub when `new Sub`
// and `this` is already an instanceof Sub
this.init();
// we throw away the instance and expect this
// array to be enriched with fields once landed because ...
return [];
}
init() {
console.log('I am root 🪵');
}
}
class Sub extends Root {
kind = 'mermade';
constructor() {
// here the instanceof Sub *already exists* but
// it cannot be accessed unless super() happens
super();
// super overrided the return type
console.log(this.init); // undefined
}
init() {
console.log('I am sub 🏊');
}
}
(new Sub).kind;
// logs 'I am sub 🏊' then `undefined`
// but it also returns marmade
// we completely lost the inheritance
new Sub instanceof Sub; // false
new Sub instanceof Root; // false
Basically this example shows that using a different instance as returned value in the super constructor accidentally pollutes such value with classes fields that don't belong to it (Liskov substitution principle anyone?), like the init
method doesn't belong to it and anything else in the class. Indeed the class inheritance is broken and the fields attached to something else that is not the class instance these are supposed to be attached.
A great example of how mind twisting is the current approach to me, rather than a valid point to justify the current state ... let's put it down in booleans:
class A {
static value = false;
get value() { return false; }
shenanigan = false;
constructor() {
console.log(
this.constructor.value, // true
this.value, // true
this.shenanigan, // false
);
return {};
}
}
class B extends A {
static value = true;
shenanigan = true;
get value() { return true; }
constructor() {
super();
console.log(
!!this.constructor.value, // false
!!this.value, // false
this.shenanigan, // true
);
}
}
new B;
so ... back to the question:
You don't, because the instance is lost in this case so that nothing undesired or surprising happens, as it is now instead.
edit by "you don't" I mean that it's not an issue, you just install fields to the currently created instance right before super call which is always safe and what developers expect ... what happens with the returned value, if changed, is a developer choice, not a problem to solve ... and no developer would expect a differently returned value to be enriched only with classes fields it doesn't belong to instead of being, dunno, upgraded as instanceof the Class.