0

One of the advantages of Javascript property syntax is that it allows to start implementation with simple "value"-type properties and only at a later point decide that a setter/getter property is necessary, without changing the API. Similarly there are use cases where a "value"-type property is sufficient for a base class, while its subclass may need setters/getters for maybe syntax checking. This is where I ran into problems with their corresponding constructors. Consider the following toy classes

class Base {
    //prop; // uncommenting this line changes behavior. Sub property getter/setter for 'prop' will then be ignored

    constructor(v) {
        this.prop = v;
    }
}

class Sub extends Base {
    #prop;

    constructor(v) {
        super(v);
    }

    set prop(v) {
        console.log("setting sub-prop " + v);
        this.#prop = v;
    }

    get prop() {
        console.log("getting sub-prop " + this.#prop);

        return this.#prop;
    }
}

const base = new Base(10);
const sub = new Sub(20); // TypeError

This snippet leads to an error "TypeError: can't set private field: object is not the right class". Similarly in Firefox it says: "Uncaught TypeError: can't set private field: object is not the right class". I have a couple of issues with this error (they all boil down to the question: "why is this error thrown?"):

  1. at the place where it is thrown (when the super-constructor calls the setter for "prop" defined in Sub), this already carries the Sub-prototype in its property __proto__, so it looks to me to be "in the right class"
  2. if the constructor were not a constructor but instead a method (say test()) of the base class, we could do the call from the Sub-instance sub without an error thrown: sub.test() .
  3. I understand that it is bad practice to use virtual methods in constructors in particular of base classes (i.e. methods that can be overriden in subclasses). However, in this case the base class rather innocently sets a member and it is really the SubClass responsibility to define getters/setters that are consistent with a property of type value. One might argue that the Error prevents the user to do inconsistent overridings, but then one could simply replace the private member "#prop" by a pseudo-private (but actual public) member "_prop" and it would be allowed and work as expected. So I don't see a benefit of this Error been thrown.

So why is this error thrown, and how should one deal with it, if maybe the base class is not under one's own control?

P.S.: Interestingly, the type error does not appear if in the base class Base the property "prop" is explicitly declared, as commented out in the code. However, in this case the setter defined in the Sub prototype is simply ignored and the newly generated Object of type "Sub" will have the property "prop" with a descriptor of type "value". This 'puzzle' is not part of this question (it might become a separate one, if I can't figure it out), but in the context of this question I find it at least worth mentioning.

Sebastian
  • 365
  • 3
  • 17
  • 1
    It seems to be a name clash. Maybe you could try removing the `prop` property from `this` in `Sub`'s constructor? – evolutionxbox Aug 03 '23 at 08:28
  • @evolutionxbox your comment sounds to me like a comment about my P.S.-remark? Because there indeed the declaration of "prop" in the base class defines an ownProperty to "this" which would need to be removed, in order for the prototype of Sub to come into the game. However, removing the property can only be done after the super-constructor returns. Which means that also initialisation needs to be done again. So `delete this.prop; this.#prop = v;`. This would be a work-around, but only in the case where prop is explicitly declared in the super class. – Sebastian Aug 03 '23 at 08:45
  • "*without changing the API*" - actually the API does change, since the property accesses now do something different (e.g. the assignment may throw an exception which it previously didn't). But the *code* doesn't need to be rewritten. – Bergi Aug 03 '23 at 08:48
  • @Bergi thanks for the comment. I was under the impression that in Javascript exceptions are not part of the interface (in contrast to checked exceptions e.g. in Java) and that therefore its fair to say that the API doesn't change. But I see what you mean and I also understand the risks coming with these changes ... – Sebastian Aug 03 '23 at 08:56
  • 1
    See https://stackoverflow.com/questions/61237153/access-private-method-in-an-overriden-method-called-from-the-base-class-constructor, https://stackoverflow.com/questions/3404301/whats-wrong-with-overridable-method-calls-in-constructors and https://stackoverflow.com/questions/47820030/call-a-child-method-from-a-parent-class-in-es6 – Bergi Aug 03 '23 at 08:56
  • @Bergi thanks a lot for the links, in particular the first one which I couldn't find myself and which is making mine a duplicate to a great extent. Still I think that turning a value-property into a getter/setter property is a particular case where I don't understand why it wasn't taken into account when designing private members. But if you think this question should be closed as a dupe, I would understand. – Sebastian Aug 03 '23 at 09:04
  • @Bergi in your comments to the first link, you make an interesting statement that the reason is all about instance fields that are not available in the super-constructor yet and that there is no difference between private and public. I understand your argument in the context of that question (with the private field being a function), but here (with the private field being just a value) there is a difference when switching to a public "_prop" as mentioned in my point 3. Is it, because public properties are simply created, when still missing and private ones not? – Sebastian Aug 03 '23 at 09:27
  • to clarify my last comment: In my context I'm setting the private property in the constructor, while in the other questions context, the private property is read and then executed as a function. Therefore, in my context, when I replace the private property by a public one it works (setting a not yet existing public property), while in the other question's context it still doesn't work (reading a not yet existing property, no matter if public or private). So in my context switching from private to public makes a difference in contrast to in the other question. – Sebastian Aug 03 '23 at 09:52
  • 1
    "*Is it, because public properties are simply created, when still missing and private ones not?*" - yes exactly. The normal (public, underscored) property simply gets created when being assigned in the setter during the parent constructor execution. The private property is not yet initialised and throws an exception if assigned at that time. – Bergi Aug 03 '23 at 10:39

0 Answers0