Как `super` из конструктора смог дотянуться до метода расширяемого класса? Метод же лежит в `A.prototype`. Или это специальное поведение?

'use strict';
class A {
 f() {console.log('hello')}
}
class B extends A {
  constructor() {
   super();
   
   // super.f() - это вызов функции на родительском объекте
   console.log(B.__proto__ == A); // Родителем является А
   console.log(A.f === undefined); // Функции такой нет
   console.log(A.__proto__.f === undefined); // И в прототипе нет
   console.log(super.f == A.prototype.f); // Я знаю где она!
   console.log(A.__proto__ != A.prototype); // Но как же мы до неё добрались?
   super.f();
   
  }
  f() {console.log('bye')} // Если бы super применялся к this
  
}

new B();


Ответы (2 шт):

Автор решения: radar4ick

Говоря простым языком: используя extends вы должны вызывать super() чтобы унаследовать свойства и методы родителя, если этого не сделать, то при первом обращении к this у Вас будет ошибка.

Поэтому ничего удивительного в том, что есть доступ к функции f.

Вот интересная статейка с подробными примерами

→ Ссылка
Автор решения: Grundy

В данном случае ошибка происходит из предположения, что

A.__proto__ === A.prototype

Однако это не так:

  1. A.prototype - это просто свойство A хранящее объект, со свойствами описанными в теле класса.
  2. A.__proto__ - это свойство A, значение которое получается при помощи метода Object.getPrototypeOf(A).

Данные два свойства никак не связаны между собой.


Чтобы разобраться что откуда берется, обратимся к спецификации

ClassDefinitionEvaluation

В 6 пункте, в части g определяются значения

 protoParent = superClass.prototype;
 constructorParent = superclass;

Далее создается proto

 proto = Object.create(protoParent);

Далее создается constructorInfo

constructorInfo = DefineMethod(proto, constructorParent)

DefineMethod

  • создается сама функция: closure = FunctionCreate(...), прототипом устанавливается переданный constructorParent
  • Вызывается MakeMethod: MakeMethod(closure, proto).

MakeMethod

В этом методе для переданной функции устанавливается внутреннее поле [[HomeObject]]: closure.[[HomeObject]] = proto

Итого

В результате выполнения, в функции конструктора установлено внутренне поле [[HomeObject]], в котором хранится объект proto - прототип класса (Для примера кода из вопроса - B.prototype).


При вызове функции для нее создается EnvironmentRecord, которому в его поле [[HomeObject]] присваивается значение одноименного поля вызываемой функции.


Super

Теперь рассмотри работу ключевого слова super, при обращении к свойству

Как видно из алгоритма все сводится к вызову метода MakeSuperPropertyReference

MakeSuperPropertyReference(actualThis, propertyName, strictFlag)

MakeSuperPropertyReference

В этом методе у текущего EnvironmentRecord вызывается метод GetSuperBase


GetSuperBase

В этом методе берется значение envRec.[[HomeObject]], как указано выше, в нем хранится прототип текущего объекта.

home = envRec.[[HomeObject]];

Возвращает метод результат выражения

home.[[GetPrototypeOf]]()

Что для нашего случая является значением protoParent.


Общий итог:

обращение к super сводится к следующему:

super.identifier

это

proto = Object.getPrototypeOf(this); // B.prototype

parentProto = Object.getPrototypeOf(proto); // A.prototype

parentProto.identifier
→ Ссылка