Сегодня я освежил свои знания о классах в JavaScript. Ниже приведены некоторые случайные факты, которые мне показались забытыми или неожиданными.

Ключевое слово [супер] не только для классов

Это может показаться удивительным, поскольку super обычно используется внутри определений классов.
Однако технически допустимо использовать ключевое слово super внутри «обычных» объектов:

let obj = {
   foo() {
      super.sayHi();
   }
}
let parent = {
   sayHi: () => console.log('hi!')
};
obj.__proto__ = parent;
obj.foo(); // hi!

То, что здесь делает super, просто относится к прототипу объекта, который использовался внутри. Хитрость в том, что super нужно использовать внутри метода, а не обычной функции или свойства функции:

let obj = {
   foo: function() {
      super.sayHi(); // SyntaxError: 'super' keyword unexpected here
   }
}

Объектные методы — это не просто «синтаксический сахар».

Несмотря на то, что MDN говорит о методах объекта

Это сокращение для функции, присвоенной имени метода.

существует, согласно спецификации ECMAScript, фактическая разница между этим типом определения

let obj = {
   foo() {}
}

…и это

let obj = {
   foo: function() {}
}

Когда механизм JS встречает объявление метода внутри объекта, он присваивает этой функции специальное свойство [[HomeObject]]. Официальное определение [[HomeObject]]:

Если функция использует super, это объект, чей [[GetPrototypeOf]] предоставляет объект, с которого начинается поиск свойств super.

Другими словами, [[HomeObject]] содержит ссылку на объект, в котором объявлен метод, использующий super. super обращается к родителю через это свойство. Вот почему мы получаем SyntaxError при попытке использовать super внутри обычной функции.

Базовые классы и производные классы используют разные типы конструкторов.

Хотя они могут показаться одинаковыми, между конструкторами базовых классов и конструкторами производных классов есть невидимая, но важная разница:

class Base {
   constructor() {}
};
class Derived extends Base {
   constructor() {}
};

В случае, когда конструктор принадлежит производному классу, специальному внутреннему свойству функции-конструктора [[ConstructorKind]] присваивается значение `derived`; в противном случае устанавливается значение `base`.

Когда вызывается базовый конструктор, он создает пустой объект и присваивает его this — чего не происходит при вызове производного конструктора.

По этой причине вызов super() внутри конструктора производного класса является обязательным и должен выполняться до использования this:

class Derived extends Base {
   constructor(value) {
      super();
      this.value = value;
   }
};

Классы используют две цепочки прототипов для наследования

Одна цепочка прототипов используется для наследования свойств и методов от экземпляров родительских классов к экземплярам дочерних классов:

Derived.prototype.__proto__ === Base.prototype

Другая цепочка прототипов используется для наследования статических членов.

Derived.__proto__ == Base

Это все на данный момент. Спасибо за чтение!