javascriptoopproperties

Problems with inherited method in JS


I have problem with an inherited method. I created a class like this:

class Person {
  #firstName;
  #lastName;
  #birthYear;
  #age;

  constructor(firstName, lastName, birthYear) {
    this.#firstName = firstName;
    this.#lastName = lastName;
    this.#birthYear = birthYear;
    this.#age = this.calculateAge();
  }

  calculateAge() {
    let thisYear = new Date().getFullYear();
    return thisYear - this.#birthYear;
  }

}

And after that a subclass like this:

class Student extends Person {
  #firstName;
  #lastName;
  #birthYear;
  #age;

  constructor(firstName, lastName, birthYear) {
    //super(firstName, lastName, birthYear); --- does not work with private properties
    super();                                  // have to call it empty?
    this.#firstName = firstName;
    this.#lastName = lastName;
    this.#birthYear = birthYear;
    this.#age = this.calculateAge();
}
  calculateAge() {
    let thisYear = new Date().getFullYear();
    return thisYear - this.#birthYear;
  }
}

If I create an instance of Student the calculateAge() method throws a TypeError because it tries to reach the superclass's #birthYear.

Very interesting, because if I call introduceSelf() on the same instance it works like a charm. It works even if I do not declare it in the subclass, so inheritance works there fine.

calculateAge() should not work without declaring in the subclass, it should throw the TypeError it does anyway. But why if it is declared in the subclass? 'this' should point to the instance, right?

If I rename it like ageCalc() and do not touch the statements at all (and rename it where I call it, in the constructor) it works just fine.

I just don't get it. An explanation what goes South under the hood would be very much appreciated!


Solution

  • In javascript, private fields are not inherited - but methods are. And this is the root cause to why the methods cast exceptions.

    If I create an instance of Student the calculateAge() method throws a TypeError because it tries to reach the superclass's #birthYear.

    A subclass will always try to access its own private variables. The issue is because #birthYear in the Student class is initialized after the call to super(), but it is not accessible before that in the parent class.

    calculateAge() should not work without declaring in the subclass, it should throw the TypeError it does anyway. But why if it is declared in the subclass? 'this' should point to the instance, right?

    calculateAge is inherited and will work without declaring it in the subclass. When you declare it in the subclass, it is going to override the parent method and use its own private variables which in this case leads to race conditions in what variables are instantiated or not.

    I would rethink and simplify the Student class as such:

    class Student extends Person {
      constructor(firstName, lastName, birthYear) {
        super(firstName, lastName, birthYear);
      }
    
      introduceSelf() {
        return `Hello! I'm ${super.getFullName()}, a student and I'm ${this.calculateAge()} years old.`; // Use methods from the parent class
      }
    }
    

    Since you already have everything you need in the Person class (including the calculateAge method), there is no need to rewrite it. And since private variables aren't inherited, if you would need to access a field, you would naturally create a getter method in the parent class.