Accessing Private Fields During Object Construction in JavaScript

By Eric Lathrop on

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:

  1. The subclass constructor() runs until super() is called
  2. The base class fields are added and instantiated
  3. The base class constructor() runs
  4. The subclass fields are added and instantiated
  5. The remainder of the subclass constructor() is run

See the problem yet? It's subtle. Here's what happens in our code:

  1. The constructor() for B is run until super() is called
  2. The fields in A are added & instantiated
  3. The constructor() for A is run
  4. B.print() is run
  5. B.print() tries to access B.#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.