Inability to set value of public class field from constructor of super if that field is redefined in the inheriting class

Viewed 114

I use polymorphism with JSDoc to describe concrete implementations of some classes. Up until now I have been doing this with getters and setters; newly implemented public class fields can shorten this getter and setter boilerplate considerably.

My problem is described by the code snippet below. The value set in the constructor does not survive the redefining of the public class field. How can I redefine a public class field in an inheriting class and keep the value set in the super constructor? Is this a bug in public class fields?

class ThingHolder {
  /**@type {notKnownYet}*/
  publicFieldForThing
  constructor(thing) {
    this.publicFieldForThing = thing
    //Do stuff for all thing holders
  }
  dropMyThing() {
    //Throw not implemented
  }
  setThingField(thing) {
    this.publicFieldForThing = thing
  }
}

class RedThingHolder extends ThingHolder {
  /**@type {RedThing}*/
  publicFieldForThing
  dropMyThing() {
    //functionality for dropping a red thing with JSDoc niceties for RedThings
  }
}
class BlueThingHolder extends ThingHolder {
  /**@type {BlueThing}*/
  publicFieldForThing
  dropMyThing() {
    //functionality for dropping a blue thing with JSDoc niceties for BlueThings
  }
}

const redThing = {
  colour: "red"
}

//Setting the thing in the constructor results in unextected behaviour
const redThingHolder = new RedThingHolder(redThing)
console.assert(redThingHolder.publicFieldForThing === redThing)

//Setting the thing through an inherited method after construction works perfectly fine
redThingHolder.setThingField(redThing)
console.assert(redThingHolder.publicFieldForThing === redThing)

Edit: This question was not about my motivations. Why do public fields behave differently to getters with regard to the prototype chain?

class Parent {
  constructor() {
    console.log("prop in instance during super: ", "prop" in this)
    console.log("publicField in instance during super: ", "publicField" in this)
  }
}

class Child extends Parent {
  constructor() {
    super()
    console.log("prop in instance after super: ", "prop" in this)
    console.log("publicField in instance after super: ", "publicField" in this)
  }
  publicField
  get prop() { }
}

new Child

1 Answers

When you call the constructor of RedThingHolder class

new RedThingHolder(redThing)

the default constructor of RedThingHolder will pass the parameter redThing to the super class' constructor which will add a property publicFieldForThing on the newly created object.

Re-definition of publicFieldForThing in child classes overwrites the publicFieldForThing set inside the super class.

I feel like this is a bug and that the value should be retained automatically.

Its not a bug and redefining a property with the same name will not retain a value because you are defining a field with a name that already exists on an object. Instead of retaining, it will overwrite the existing field.

Doing this

class RedThingHolder {
   publicFieldForThing;
   ...
}

will overwrite the publicFieldForThing field with the default value of undefined.

How can I redefine a public class field in an inheriting class and keep the value set in the super constructor?

You can set the publicFieldForThing in the child class constructor using the getter for publicFieldForThing defined in the super class.

class ThingHolder {
  constructor(thing) {
    this.publicFieldForThing = thing;
  }

  get publicField() {
    return this.publicFieldForThing;
  }
}

class RedThingHolder extends ThingHolder {
  constructor(thing) {
   super(thing);
   this.publicFieldForThing = this.publicField;
  }
}

const redThing = {
  colour: 'red',
};

const redThingHolder = new RedThingHolder(redThing);
console.log(redThingHolder.publicFieldForThing === redThing);

Edit

Why do public fields behave differently to getters with regard to the prototype chain?

getter/setters in ES2015's classes are added on the proptotype object whereas the public fields are added on the object itself.

In the code example you posted, prop is added on the Child.prototype whereas the publicField is like doing

this.publicField = undefined;

in the constructor of the Child class just after the super() call.

This is why "prop" in this inside the constructor of the Parent class returns true whereas "publicField" in this evaluates to true only inside the constructor of the Child class.

Related