I think the next (simplified) code should be working:
class WfConfigurable {
constructor(config) {
this._init(config);
}
_init(config) {
this.configure(config);
}
configure(config) {}
}
class WfStateful extends WfConfigurable {
#saveSuspend = 0;
constructor(config) {
super(config);
}
get saving() { return this.#saveSuspend == 0; }
configure(config) {
this.suspendSave();
super.configure(config);
this.continueSave();
}
suspendSave() {
this.#saveSuspend++;
}
continueSave(force) {
if (force) {
this.#saveSuspend = 0;
} else {
this.#saveSuspend = Math.max(this.#saveSuspend - 1, 0);
}
}
}
class WfApp extends WfStateful {
constructor(config) {
super(config);
}
}
const wfApp = new WfApp({});
But I have got the next error message:
TypeError: Cannot read private member #saveSuspend from an object whose class did not declare it
at WfApp.suspendSave ((index):24:13)
at WfApp.configure ((index):19:14)
at WfApp._init ((index):7:14)
at new WfConfigurable ((index):4:14)
at new WfStateful ((index):15:9)
at new WfApp ((index):37:9)
at (index):41:15
For example, this works well:
class A {
#priv = 'hello';
constructor() {
console.log('A');
}
get priv() {
return this.#priv;
}
set priv(p) {
this.#priv = p;
}
}
class B extends A {}
const b = new B();
b.priv += ' babe';
console.log(b.priv);
Writes out: "hello babe"
2
Answers
The problem seems pretty obvious – the super class WfConfigurable cannot access code that uses
#saveSuspend
. In your case the super class’s constructor calls_init
which callsconfigure
which has been overridden to use#saveSuspend
.Even if
#saveSuspend
wasn’t private, it would still be a problem to use it in this manner because it wouldn’t have been initialized. This is because instance members aren’t initialized until aftersuper()
is called. So if you madesuspendSave
public and put a log call afterthis.saveSuespend++
you’d find the result would be NaN.There’s a lot of ways this can be refactored that you can figure out yourself. In general its a bad idea for constructors to call methods that can be overridden for a myriad reasons such as the JavaScript specific one you’ve discovered for in this issue.
The error message "Cannot read private member
#saveSuspend
from an object whose class did not declare it" is slightly confusing. More accurate in this case would be "from an object where it is not (yet) initialised".Private fields, and fields in general, are created on the instance (and initialised with the value defined in the class body) when the
super()
constructor returns. If that constructor is calling an overridden method (configure
) before the initialisation is done, it will not be able to access the non-existing field. With private fields, it just throws an exception because the field doesn’t exist, instead of returningundefined
like a normal property.The only solution is to never call an overrideable method from a constructor. This is true in any language.