TypeScriptでちょっとハマったのでメモ。
qiita.ts
abstract class Parent {
protected hoge: string = "Parent";
constructor() {
this.foo();
}
public abstract foo();
public bar() {
console.log(this.hoge);
}
}
class Child extends Parent {
protected hoge: string = "Child";
constructor() {
super();
}
public foo() {
console.log(this.hoge);
}
}
const child: Parent = new Child(); // Parent
child.foo();// Child
child.bar();// Child
上記のコードだとC#やJavaの感覚では、
Childが3回出力されるものだと勘違いしてあせった。
想定した出力(実際、JavaやC#で似たようなことをするとこのように出力される)
Child
Child
Child
実際の出力は
Parent
Child
Child
になる
これを把握せずにこんなコードを書くと・・・
qiita.ts
abstract class Parent {
protected hoge: string = "Parent";
protected speech: string = "";
constructor() {
this.init();
}
public abstract init();
public bar() {
console.log(this.hoge);
}
getSpeech() {
return this.speech;
}
}
class Child extends Parent {
protected hoge: string = "Child";
constructor() {
super();
}
public init() {
this.speech = `私の名前は${this.hoge}です。`
}
}
const child: Parent = new Child();
console.log(child.getSpeech());
出力結果
私の名前はParentです。
となってちょっと悩みました。
期待した出力
私の名前はChildです。
なぜそうなるか
TypeScriptではC#やJavaのようにクラスにメンバ変数を直接書くことができる。
が、JavaScriptではそのような書き方はできず、コンストラクタ内で定義することになる。
エラーになるコード
//JavaScript
class Hoge{
foo="a"
}
正しいJavaScript
//JavaScript
class Hoge{
constructor(){
this.foo="a"
}
}
なのでTypeScriptの以下のコードはビルドされると
//TypeScript
class Hoge{
foo="a"
}
このようなJavaScriptを吐き出します。
//JavaScript
class Hoge {
constructor() {
this.foo = "a";
}
}
つまり先程のよくわからない挙動をしていたTypeScriptは以下のようなJavaScriptになるためにC#やJavaに慣れ親しんだ人にとっては意図しない挙動になったということでした。
子クラスのメンバ変数に値が定義される前に親クラスのコンストラクタが実行されていることがわかります。
メンバ変数の定義がsuper()のあとに入れられてしまうという仕様のせいでした。
//TSCでコンパイルされたJavaScript
class Parent {
constructor() {
this.hoge = "Parent";
this.speech = "";
this.init();
}
bar() {
console.log(this.hoge);
}
getSpeech() {
return this.speech;
}
}
class Child extends Parent {
constructor() {
super();//<-ここに注目
this.hoge = "Child";///<-ここに注目
}
init() {
this.speech = `私の名前は${this.hoge}です。`;
}
}
const child = new Child();
console.log(child.getSpeech());