Не ошибка
Во-первых, это не ошибка в TypeScript, Babel или вашей среде выполнения JS.
Почему так должно быть
Первый вопрос, который у вас может возникнуть, — «Почему бы не сделать это правильно!?!?». Давайте рассмотрим конкретный случай испускания TypeScript. Фактический ответ зависит от того, для какой версии ECMAScript мы создаем код класса.
Эмиссия нижнего уровня: ES3/ES5
Давайте рассмотрим код, созданный TypeScript для ES3 или ES5. Я упростил + немного аннотировал это для удобства чтения:
var Base = (function () {
function Base() {
// BASE CLASS PROPERTY INITIALIZERS
this.myColor = 'blue';
console.log(this.myColor);
}
return Base;
}());
var Derived = (function (_super) {
__extends(Derived, _super);
function Derived() {
// RUN THE BASE CLASS CTOR
_super();
// DERIVED CLASS PROPERTY INITIALIZERS
this.myColor = 'red';
// Code in the derived class ctor body would appear here
}
return Derived;
}(Base));
Выброс базового класса бесспорно верен - поля инициализируются, затем запускается тело конструктора. Вы, конечно, не хотели бы обратного — инициализация полей до запуска тела конструктора означала бы, что вы не могли бы видеть значения полей до тех пор, пока после конструктора, а это не то, что кто хочет.
Является ли производный класс правильным?
Нет, вы должны поменять порядок
Многие люди утверждают, что эмиссия производного класса должна выглядеть так:
// DERIVED CLASS PROPERTY INITIALIZERS
this.myColor = 'red';
// RUN THE BASE CLASS CTOR
_super();
Это очень неправильно по целому ряду причин:
- У него нет соответствующего поведения в ES6 (см. следующий раздел).
- Значение
'red'
для myColor
будет немедленно перезаписано значением базового класса «синий».
- Инициализатор поля производного класса может вызывать методы базового класса, которые зависят от инициализации базового класса.
Что касается последнего пункта, рассмотрите этот код:
class Base {
thing = 'ok';
getThing() { return this.thing; }
}
class Derived extends Base {
something = this.getThing();
}
Если бы инициализаторы производного класса выполнялись до инициализаторов базового класса, Derived#something
всегда было бы undefined
, хотя очевидно, что должно быть 'ok'
.
Нет, вы должны использовать машину времени
Многие другие люди возразят, что нужно сделать туманное что-то еще, чтобы Base
знал, что Derived
имеет инициализатор поля.
Вы можете написать примеры решений, которые зависят от знания всего кода, который нужно запустить. Но TypeScript/Babel/etc не может гарантировать, что это существует. Например, Base
может находиться в отдельном файле, где мы не можем видеть его реализацию.
Эмиссия нижнего уровня: ES6
Если вы этого еще не знали, пришло время узнать: классы не являются функцией TypeScript. Они являются частью ES6 и имеют определенную семантику. Но классы ES6 не поддерживают инициализаторы полей, поэтому они преобразуются в код, совместимый с ES6. Это выглядит так:
class Base {
constructor() {
// Default value
this.myColor = 'blue';
console.log(this.myColor);
}
}
class Derived extends Base {
constructor() {
super(...arguments);
this.myColor = 'red';
}
}
Вместо
super(...arguments);
this.myColor = 'red';
Должны ли мы иметь это?
this.myColor = 'red';
super(...arguments);
Нет, потому что это не работает. Недопустимо ссылаться на this
перед вызовом super
в производном классе. Это просто не может работать таким образом.
ES7+: Публичные поля
Комитет TC39, контролирующий JavaScript, изучает возможность добавления инициализаторов полей в будущую версию языка.
Вы можете прочитать об этом на GitHub или ознакомьтесь с конкретным вопросом о порядке инициализации.
Обновление ООП: виртуальное поведение от конструкторов
Все языки ООП имеют общее руководство, некоторые из которых применяются явно, некоторые неявно по соглашению:
Не вызывать виртуальные методы из конструктора
Примеры:
В JavaScript мы должны немного расширить это правило.
Не наблюдайте виртуальное поведение от конструктора
а также
Инициализация свойства класса считается виртуальной
Решения
Стандартное решение — преобразовать инициализацию поля в параметр конструктора:
class Base {
myColor: string;
constructor(color: string = "blue") {
this.myColor = color;
console.log(this.myColor);
}
}
class Derived extends Base {
constructor() {
super("red");
}
}
// Prints "red" as expected
const x = new Derived();
Вы также можете использовать шаблон init
, хотя вам нужно быть осторожным, чтобы не наблюдать за его виртуальным поведением, и не делать в производном методе init
действия, требующие полной инициализации. базового класса:
class Base {
myColor: string;
constructor() {
this.init();
console.log(this.myColor);
}
init() {
this.myColor = "blue";
}
}
class Derived extends Base {
init() {
super.init();
this.myColor = "red";
}
}
// Prints "red" as expected
const x = new Derived();
person
Ryan Cavanaugh
schedule
24.04.2017