skip to Main Content

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


  1. 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 calls configure 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 after super() is called. So if you made suspendSave public and put a log call after this.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.

    Login or Signup to reply.
  2. 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 returning undefined like a normal property.

    The only solution is to never call an overrideable method from a constructor. This is true in any language.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search