I was writing some JavaScript using the new-ish class syntax, and I ran into a gotcha. Consider the following code:
class A {
constructor() {
this.print();
}
}
class B extends A {
#stuff = 42;
print() {
console.log(this.#stuff);
}
}
new B();
// prints: Uncaught TypeError: can't access private field or method: object is not the right class
What normally happens when you instantiate a subclass:
- The subclass
constructor()
runs untilsuper()
is called - The base class fields are added and instantiated
- The base class
constructor()
runs - The subclass fields are added and instantiated
- The remainder of the subclass
constructor()
is run
See the problem yet? It's subtle. Here's what happens in our code:
- The
constructor()
forB
is run untilsuper()
is called - The fields in
A
are added & instantiated - The
constructor()
forA
is run B.print()
is runB.print()
tries to accessB.#stuff
, which hasn't been created yet
I would've expected the subclass fields to be added and instantiated before the
subclass constructor()
is run, but that's not how it works. I'm not sure why
field creation works differently for base classes vs. subclasses, but maybe
there's a good reason. If you know why, email me.
The MDN page on private properties briefly describes how it works:
Like their public counterparts, private instance fields are added before the constructor runs in a base class, or immediately after
super()
is invoked in a subclass
The description doesn't really describe the implications of this process.