Что случилось с IIFE¶
JS код, сгенерированный для класса, мог бы быть таким:
1 2 3 4 5 6 7 |
|
Причина, по которой он обернут в Немедленно Вызываемое Функциональное Выражение (IIFE), например:
1 2 3 4 5 |
|
связана с наследованием. Это позволяет TypeScript захватить базовый класс как переменную _super
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
|
Обратите внимание, что IIFE позволяет TypeScript легко захватить базовый класс Point
в переменной _super
и последовательно использовать в теле класса.
__extends
¶
Вы заметите, что как только унаследуете класс, TypeScript сгенерирует следующий код:
1 2 3 4 5 6 7 8 9 10 11 |
|
Здесь d
относится к дочернему классу и b
относится к базовому. Эта функция делает 2 вещи:
- копирует статичные члены базового класса в дочерний класс:
for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p];
- устанавливает прототип функции дочернего класса на опциональный поиск членов родительского
proto
, то есть фактическиd.prototype.__proto__ = b.prototype
Люди редко имеют проблемы с пониманием 1, но многие испытывают сложности с 2. Разберемся по порядку.
d.prototype.__proto__ = b.prototype
Я нашел самое простое объяснение этой темы в процессе обучения многих людей. Сначала мы объясним, почему код из __extends
эквивалентен d.prototype.__proto__ = b.prototype
, и потом почему эта строка важна сама по себе. Чтобы понять все это, вам нужно знать следующее:
__proto__
prototype
- влияние
new
наthis
внутри вызываемой функции - влияние
new
наprototype
и__proto__
Все объекты в JavaScript содержат __proto__
. Этот член часто недоступен в старых браузерах (иногда документация ссылается на это магическое свойство как [[prototype]]
). Оно имеет одну цель: если какое-то свойство не найдено на объекте во время поиска (например obj.property
), тогда поиск будет осуществлен по obj.__proto__.property
. Если и там не найдется нужное свойство, то продолжится в obj.__proto__.__proto__.property
до тех пор, пока: не будет найдено или последний .__proto__
не будет равен null. Это объясняет, почему про JavaScript говорят, что он поддерживает прототипное наследование из коробки. Это продемонстрировано в следующем примере, который вы можете запустить в консоли Хрома или Node.js:
1 2 3 4 5 6 7 8 9 10 11 |
|
Теперь вы поняли __proto__
. Другой полезный факт состоит в том, что все function
в JavaScript имеют свойство prototype
, а prototype
в свою очередь имеет constructor
, указывающий на саму function
. Как показано ниже:
1 2 3 4 5 6 7 |
|
Теперь посмотрим как влияет new
на this
внутри вызываемой функции. По сути this
внутри вызываемой функции указывает на созданный объект, который будет возвращен функцией. Это легко увидеть, если изменить значение какого-нибудь свойства на this
внутри функции:
1 2 3 4 5 6 7 |
|
Теперь единственное, что вам нужно знать, это то, что вызов new
для функции присваивает prototype
этой функции на __proto__
нового созданного объекта, который функция возвращает как результат вызова. Далее код, который можно запустить для большего понимания:
1 2 3 4 5 |
|
Вот и все. Теперь посмотрим на __extends
. Я пронумеровал строки:
1 2 3 |
|
Обратное чтение этой функции d.prototype = new __()
в 3 строке фактически означает, что d.prototype = {__proto__ : __.prototype}
(из-за влияния new
на prototype
и __proto__
), комбинируя его с предыдущей строкой (строка 2 __.prototype = b.prototype;
), вы получаете d.prototype = {__proto__ : b.prototype}
.
Но подождите, мы же хотели d.prototype.__proto__
, т.е. просто изменить proto и сохранить старый d.prototype.constructor
. Вот где значение первой строки (function __() { this.constructor = d; }
) вступает в силу. Здесь мы будем фактически иметь d.prototype = {__proto__ : __.prototype, constructor : d}
(из-за влияния new
на this
внутри вызываемой функции). Итак, поскольку мы восстанавливаем d.prototype.constructor
, единственное, что мы действительно мутировали, это __proto__
, следовательно, d.prototype.__proto__ = b.prototype
.
d.prototype.__proto__ = b.prototype
значение
Это значение и есть то, что позволяет добавлять члены-функции к дочернему классу и наследовать другие от базового класса. Это показано на следующем примере:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
|
По сути bird.fly
будет найден в bird.__proto__.fly
(помните, что вызов new
присвоит bird.__proto__
все содержимое Bird.prototype
) и bird.walk
(унаследованный член) будет найден в bird.__proto__.__proto__.walk
(как bird.__proto__ == Bird.prototype
и bird.__proto__.__proto__
== Animal.prototype
).