Как `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 шт):
Говоря простым языком: используя extends вы должны вызывать super() чтобы унаследовать свойства и методы родителя, если этого не сделать, то при первом обращении к this у Вас будет ошибка.
Поэтому ничего удивительного в том, что есть доступ к функции f.
Вот интересная статейка с подробными примерами
В данном случае ошибка происходит из предположения, что
A.__proto__ === A.prototype
Однако это не так:
A.prototype- это просто свойствоAхранящее объект, со свойствами описанными в теле класса.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