私はいくつかのコードを書きました:
class Base {
// Default value
myColor = 'blue';
constructor() {
console.log(this.myColor);
}
}
class Derived extends Base {
myColor = 'red';
}
// Prints "blue", expected "red"
const x = new Derived();
派生クラスフィールドの初期化子が、基本クラスコンストラクターの前に実行されることを期待していました。代わりに、派生クラスは、基本クラスコンストラクターが実行されるまでmyColor
プロパティを変更しないため、コンストラクターの誤った値を確認します。
これはバグですか?どうしましたか?なぜこれが起こるのですか?代わりに何をすべきですか?
まず、これはTypeScript、Babel、またはJSランタイムのバグではありません。
あなたが持っているかもしれない最初のフォローアップは「なぜこれをしないのか正しく!?!?」です。 TypeScriptエミットの特定のケースを調べてみましょう。実際の答えは、クラスコードを出力するECMAScriptのバージョンによって異なります。
ES3またはES5のTypeScriptによって生成されたコードを調べてみましょう。読みやすくするために、これに少し+注釈を付けました。
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));
基本クラスの出力は議論の余地なく正しいです-フィールドが初期化され、その後コンストラクター本体が実行されます。あなたは確かに反対を望んでいないでしょう-フィールドの初期化beforeコンストラクタ本体を実行することはafterまでフィールド値を見ることができなかったことを意味しますが、これは何ですか誰もが望んでいる。
派生クラスは正しいエミットですか?
多くの人は、派生クラスの出力は次のようになるはずだと主張します。
// DERIVED CLASS PROPERTY INITIALIZERS
this.myColor = 'red';
// RUN THE BASE CLASS CTOR
_super();
これはいくつかの理由で非常に間違っています:
'red'
for myColor
は、基本クラス値 'blue'によってすぐに上書きされます最後の点で、次のコードを検討してください。
class Base {
thing = 'ok';
getThing() { return this.thing; }
}
class Derived extends Base {
something = this.getThing();
}
派生クラス初期化子が基本クラス初期化子の前に実行された場合、Derived#something
は常にundefined
ですが、明らかに'ok'
。
他の多くの人々は、Base
がDerived
にフィールド初期化子があることを認識できるように、曖昧な何か他のを実行する必要があると主張します。
実行するコード全体を知ることに依存するソリューションの例を書くことができます。しかし、TypeScript/Babel /などは、これが存在することを保証できません。たとえば、Base
は、その実装を確認できない別のファイルに置くことができます。
これをまだご存じない場合は、学習する時間です。クラスは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
を参照することはできません。それは単にこのように機能することはできません。
JavaScriptを管理するTC39委員会は、フィールド初期化子を将来のバージョンの言語に追加することを調査しています。
GitHubで読んでください または 初期化の順序に関する特定の問題を読んでください 。
すべてのOOP言語には一般的なガイドラインがあり、一部は明示的に実施され、一部は慣例により暗黙的に実施されます。
コンストラクターから仮想メソッドを呼び出さない
例:
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
パターンを使用することもできますが、注意してnotそこから仮想動作を観察するand派生した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();