From Classes on mdn web docs:
Public field declarations
class Rectangle { height = 0; width; constructor(height, width) { this.height = height; this.width = width; } }
Aren’t the height
and width
outside of the constructor
meaningless since they’re always overwritten in the constructor
?
What’s the significance of the height
and width
declaration/initialization outside of constructor
in this case?
Are they used/reachable by any means?
Advertisement
Answer
The properties are only declared once (outside the constructor); if you didn’t have those declarations, there’d be no declarations for those properties (they’d be created by assignment instead). Public property declarations (aka “public class fields”) are optional, but can help avoid shape changes in objects (which improves efficiency) and/or reduce the analysis JavaScript engines need to do on the object being created to avoid shape changes. Basically, they provide a declarative (rather than imperative [step-by-step]) means of saying (in this case) that Rectangle
objects always have a height
and width
property. While the JavaScript engine can figure that out using analysis of the constructor code, the declarations mean it doesn’t have to worry about that (for those properties).
Aren’t the height and width outside of the constructor meaningless since they’re always overwritten in the constructor?
The initializer (= 0
) on the height
property is pointless, yes, since it gets unambiguously overwritten by the code in the constructor and the assignment doing so is the first statement in the constructor. If it weren’t the first statement, then code in the constructor would be able to observe the 0
in height
prior to the assignment.
Are [the height and width declaration/initialization outside of constructor] used/reachable by any means?
They could be used by subsequent class field initializers. For instance, this is valid:
class Example { a = 2; b = this.a * 3; } console.log(new Example().b); // 6
It’s probably also worth pointing out that there’s a difference if the class is a subclass: When you use the declaration syntax, the property is created via “define” semantics (as though you used the Object.defineProperty
function), whereas if you just assign to it you’re using assignment semantics. That matters if the superclass also defined the property:
class Super { #example = 42; get example() { console.log("Getting example"); return this.#example; } set example(value) { console.log("Setting example"); this.#example = value; } } class Sub extends Super { example = 67; } const super1 = new Super(); console.log(`What kind of property is super1.example?`); console.log(propertyKind(super1, "example")); const sub1 = new Sub(); console.log(`What kind of property is sub1.example?`); console.log(propertyKind(sub1, "example")); function propertyKind(obj, name) { let descr; do { descr = Object.getOwnPropertyDescriptor(obj, name); if (descr) { break; } obj = Object.getPrototypeOf(obj); if (!obj) { break; } } while (!descr); return descr ? "value" in descr ? "data" : "accessor" // presumably : "none"; }
Notice that example
is an accessor property on super1
(via inheritance from its prototype), but it’s a data
property on sub1
because it was redeclared by Sub
. If Sub
had just assigned to it, it would have just used the property Super
created:
class Super { #example = 42; get example() { console.log("Getting example"); return this.#example; } set example(value) { console.log("Setting example"); this.#example = value; } } class Sub extends Super { constructor() { super(); this.example = 67; } } const super1 = new Super(); console.log(`What kind of property is super1.example?`); console.log(propertyKind(super1, "example")); const sub1 = new Sub(); console.log(`What kind of property is sub1.example?`); console.log(propertyKind(sub1, "example")); function propertyKind(obj, name) { let descr; do { descr = Object.getOwnPropertyDescriptor(obj, name); if (descr) { break; } obj = Object.getPrototypeOf(obj); if (!obj) { break; } } while (!descr); return descr ? "value" in descr ? "data" : "accessor" // presumably : "none"; }
Notice that now, it’s an accessor property on both sub1
and super1
(and that when Sub
did its assignment, the setter code in Super.example
ran).
TL;DR – I would keep the declarations for clarity and potential efficiency, but remove the = 0
initializer on height
, since that value is clearly never meant to be used.