DRYな備忘録

Don't Repeat Yourself.

TypeScriptの継承とかsuper呼び出しとかメモ

問題

継承クラスで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.familyNameundefinedのままなのか。でも、__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 王国.

f:id:otiai10:20140226172118j:plain




DRYな備忘録として