問題
継承クラスでsuper
の明示的な呼び出しがなくても動くような気がしたのでちょっとメモる
環境構築
% cd % mkdir hoge % cd hoge % npm --version 1.1.62 % npm search typescript # 略 % npm install --local typescript # 前略 npm WARN prefer global typescript@0.9.7 should be installed with -g typescript@0.9.7 node_modules/typescript % vi hoge.ts
class Base { constructor() { console.log('this is constructor of Base'); } } class Child extends Base { constructor() { console.log('this is constructor of Child'); } } var child = new Child(); console.log('This is instance of Child ->', child);
コンパイルしてみる
% ./node_modules/typescript/bin/tsc hoge.ts /Users/otiai10/proj/typescript/hoge/hoge.ts(8,5): error TS2105: Constructors for derived classes must contain a 'super' call.
継承クラスのコンストラクタでは親のコンストラクタをまず呼ぶべき?
エラーは出るものの、hoge.js
は生成されている模様。ちょっと実行してみる
% node hoge.js this is constructor of Child This is instance of Child -> {}
なるほど。親クラスのコンストラクタが呼ばれていない。extends
キーワード使っても、結局はsuper();
を実行しないと親コンストラクタは呼ばれて居ないっぽい。
ちょっと実験。親クラスで定義したものを、super();
を実行していない継承クラスで使ってみる。コンパイルからして失敗する気がする。
class Base { familyName: string; constructor() { console.log('this is constructor of Base'); this.familyName = 'Otiai'; } } class Child extends Base { firstName: string; constructor() { console.log('this is constructor of Child'); this.firstName = '10'; } greeting(): string { return this.firstName + ' ' + this.familyName; } } var child = new Child(); console.log('This is instance of Child ->', child); console.log(child.greeting());
% rm hoge.js % ./node_modules/typescript/bin/tsc hoge.ts /Users/otiai10/proj/typescript/hoge/hoge.ts(11,5): error TS2105: Constructors for derived classes must contain a 'super' call.
ん?エラーメッセージ変わってなくね?
% node hoge.js this is constructor of Child This is instance of Child -> { firstName: '10' } 10 undefined
ほーぅundefined
でjs作ってしまうかー...
いい加減、コンパイル後のjsを見てみる。コメントは加筆。
this is constructor of Child // 継承`extends`の定義 var __extends = this.__extends || function (d, b) { /** * プロパティの移植 * 親クラスにあるプロパティを全て継承クラスに移す */ for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; /** * 継承クラス自身を * コンストラクタの定義とする * __オブジェクトの定義 */ function __() { this.constructor = d; } /** * __オブジェクトのprototypeを * 親クラスのprototypeで上書き */ __.prototype = b.prototype; /** * 継承クラスのプロトタイプを__で上書き */ d.prototype = new __(); /** * jsのオブジェクトは参照渡しなので、 * この時点でdはbのプロパティとメソッドを持っている */ }; // Base自身を返すクロージャの定義 var Base = (function () { function Base() { console.log('this is constructor of Base'); this.familyName = 'Otiai'; } return Base; })(); // 親を取って自分をextendsして自身を返すクロージャ var Child = (function (_super) { __extends(Child, _super); function Child() { console.log('this is constructor of Child'); this.firstName = '10'; } Child.prototype.greeting = function () { return this.firstName + ' ' + this.familyName; }; return Child; })(Base);//ここがみそ。_super === Base となる var child = new Child(); console.log('This is instance of Child ->', child); console.log(child.greeting());
なるほど、Base
のコンストラクタを呼ばないとthis.familyName
はundefined
のままなのか。でも、__extends
関数の中でプロパティの移植をしているから、static
なプロパティだったらsuper();
を呼ばずに継承できるのではないか?
@@ -1,5 +1,6 @@ class Base { familyName: string; + static country: string = 'Japan'; constructor() { console.log('this is constructor of Base'); this.familyName = 'Otiai'; @@ -14,7 +15,7 @@ } greeting(): string { - return this.firstName + ' ' + this.familyName; + return this.firstName + ' ' + this.familyName + ', came from ' + this.country; } }
% ./node_modules/typescript/bin/tsc hoge.ts /Users/otiai10/proj/typescript/hoge/hoge.ts(12,5): error TS2105: Constructors for derived classes must contain a 'super' call. /Users/otiai10/proj/typescript/hoge/hoge.ts(18,79): error TS2094: The property 'country' does not exist on value of type 'Child'.
ぉこされた。作られたjsの差分は?
var Base = (function () { function Base() { console.log('this is constructor of Base'); this.familyName = 'Otiai'; } + Base.country = 'Japan'; return Base; })(); var Child = (function (_super) { __extends(Child, _super); function Child() { @@ -41,10 +20,10 @@ this.firstName = '10'; } Child.prototype.greeting = function () { - return this.firstName + ' ' + this.familyName; + return this.firstName + ' ' + this.familyName + ', came from ' + this.country; }; return Child; })(Base); var child = new Child(); console.log('This is instance of Child ->', child);
となっている
jsのプロトタイプの理解が間違っている気がする
コンパイル後のjsに、こういうコードを入れて調べてみる
var __extends = this.__extends || function (d, b) { - for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; + for (var p in b) { + console.log('Base has own property? ', p, b.hasOwnProperty(p)); + if (b.hasOwnProperty(p)) d[p] = b[p]; + console.log('Embeded to derived class? ', d[p]); + } function __() { this.constructor = d; } __.prototype = b.prototype; d.prototype = new __();
結果
% node hoge.check.js Base has own property? country true Embeded to derived class? Japan this is constructor of Child This is instance of Child -> { firstName: '10' } 10 undefined, came from undefined
__extends
メソッドの最初の部分は、オブジェクトとしての{}
のプロパティを移植する処理なので、ここにはcountry:Japan
はある。でもChildren.prototype
に生えているわけじゃないから、this.country
では参照できない。
いいかげん普通にTypeScript書く
これで文句無いはず!
class Base { familyName: string; static country: string = '王国'; constructor(public age: number) { this.familyName = '田村'; } } class Child extends Base { firstName: string; constructor() { super(17); this.firstName = 'ゆかり'; } greeting(): string { var greetings = "Hi, my name is " + this.firstName + " " + this.familyName + ",\n"; greetings += this.age + " years old,\n"; greetings += "came from " + Child.country + "."; return greetings; } } var child = new Child(); console.log('This is instance of Child ->', child); console.log(child.greeting());
コンパイルで生成されたjsは...
var __extends = this.__extends || function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; function __() { this.constructor = d; } __.prototype = b.prototype; d.prototype = new __(); }; var Base = (function () { function Base(age) { this.age = age; this.familyName = '田村'; } Base.country = '王国'; return Base; })(); var Child = (function (_super) { __extends(Child, _super); function Child() { _super.call(this, 17); this.firstName = 'ゆかり'; } Child.prototype.greeting = function () { var greetings = "Hi, my name is " + this.firstName + " " + this.familyName + ",\n"; greetings += this.age + " years old,\n"; greetings += "came from " + Child.country + "."; return greetings; }; return Child; })(Base); var child = new Child(); console.log('This is instance of Child ->', child); console.log(child.greeting());
だいたい予想通りですね!
雑感
- JavaScriptにおけるconstructorとprototypeとは?
みたいなレベルの学習が必要な気がする。これかな
読むべきものが溜まっていってつらい...
秘密のドアから会いに来て!
% node hoge.js This is instance of Child -> { age: 17, familyName: '田村', firstName: 'ゆかり' } Hi, my name is ゆかり 田村, 17 years old, came from 王国.
DRYな備忘録として