Сегодня я освежил свои знания о классах в 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
Это все на данный момент. Спасибо за чтение!