クラスの機能を実現するためにコンパイル後に生成されるJavaScriptは、以下のようなものかもしれません:
コピー function Point ( x , y ) {
this . x = x ;
this . y = y ;
}
Point . prototype . add = function ( point ) {
return new Point ( this . x + point . x , this . y + point . y) ;
}; TypeScriptが生成するクラスは、Immediately-Invoked Function Expression(IIFE)に包まれています。IIFEの例:
コピー ( function () {
// BODY
return Point ;
} )() ; クラスがIIFEに包まれている理由は、継承に関係しています。TypeScriptは、IIFEによってベースクラスを変数_superとして補足(キャプチャ)できるようにしています:
コピー var Point3D = ( function ( _super ) {
__extends (Point3D , _super) ;
function Point3D ( x , y , z ) {
_super . call ( this , x , y) ;
this . z = z ;
}
Point3D . prototype . add = function ( point ) {
var point2D = _super . prototype . add . call ( this , point) ;
return new Point3D ( point2D . x , point2D . y , this . z + point . z) ;
};
return Point3D ;
} )(Point) ; TypeScriptは、IIFEによって、簡単にベースクラス Pointを_super変数に捕捉できるようにしており、かつ、それがクラスの本体で一貫的に使用されていることに注意してください。
クラスを継承するとTypeScriptが次のような関数を生成することに気づくでしょう:
ここで dは派生クラス(d erived class)を参照し、bはベースクラス(b ase class)を参照します。この関数は次の2つのことを行います:
親クラスの静的メンバを子クラスにコピーする:for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p];
子クラス関数のプロトタイプを準備し、必要に応じて親のprotoのメンバを検索できるようにする。つまり、実質的に d.prototype.__proto__ = b.prototype です。
1を理解するのに苦労する人はほとんどいませんが、2については多くの人が理解に苦しみます。そのため、順番に説明します。
d.prototype.__proto__ = b.prototype
これについて多くの人を教えた結果、次のような説明が最もシンプルだと分かりました。まず、__extendsのコードが、単純なd.prototype.__proto__ = b.prototypeとどうして同じなのか、そしてなぜ、この行それ自体が重要であるのかを説明します。これをすべて理解するためには、これらのことを理解する必要があります:
newのprototypeと__proto__に対する効果
JavaScriptのすべてのオブジェクトは __proto__メンバを含んでいます。このメンバは古いブラウザではアクセスできないことがよくあります(ドキュメントでは、この魔法のプロパティを [[prototype]]と呼ぶことがあります)。これは1つの目的を持っています:検索しているプロパティがオブジェクトに見つからない場合(例えば obj.property)、obj.__proto__.propertyを検索します。それでもまだ見つからなければ、 obj.__proto__.__proto__.propertyを検索します: それが見つかるか、最後の.__proto__自体がnullとなるまで続きます。これは、JavaScriptが_プロトタイプ継承_(prototypal inheritance)をサポートしているということです。次の例でこれを示します。chromeコンソールまたはNode.jsで実行できます:
これで、あなたは __proto__を理解できました。もう1つの便利な事実は、JavaScriptの関数(function)にはprototypeというプロパティがあり、そして、そのconstructorメンバは、逆に関数を参照しているということです。これを以下に示します:
次に、呼び出された関数内のthisに対してnewが及ぼす影響を見てみましょう。基本的に、newを使って呼び出された関数の内側のthisは、関数から返される新しく生成されたオブジェクトを参照します。関数内でthisのプロパティを変更するのは簡単です:
ここで理解しておくべきことは、関数に対するnewの呼び出しにより、関数のprototypeが、新しく生成されたオブジェクトの__proto__に設定されることです。次のコードを実行することによって、それを完全に理解できます:
これだけのことです。今度は__extendsの抜粋を見てください。これらの行に番号を振りました:
この関数を逆から見ると、3行目のd.prototype = new __()は、 d.prototype = {__proto__ : __.prototype}を意味します(prototypeと__proto__に対するnewの効果によるものです)。それを2行目(__.prototype = b.prototype;)と組み合わせると、d.prototype = {__proto__ : b.prototype}となります。
しかし、待ってください。私達は、単にd.prototype.__proto__だけが変更され、d.prototype.constructorは、それまで通り維持されることを望んでいました。そこで重要な意味があるのが、最初の行(function __() { this.constructor = d; })です。これによってd.prototype = {__proto__ : __.prototype, constructor : d}と同じことを実現できます(これは関数の内側のthisに対するnewによる効果のためです)。したがって元のd.prototype.constructorを復元しているので、ここで変更されたものは、__proto__だけです。なのでd.prototype.__proto__ = b.prototypeとなります。
d.prototype.__proto__ = b.prototypeの意味
これを行うことによって、子クラスにメンバ関数を追加しつつ、その他のメンバは基本クラスから継承することができます。次の簡単な例で説明します:
基本的にbird.flyはbird.__proto__.fly(newはbird.__proto__の参照をBird.prototypeに設定することを思い出してください)から検索され、bird.walk(継承されたメンバ)はbird.__proto__.__proto__.walkから検索されます(bird.__proto__ == Bird.prototype、そして、bird.__proto__.__proto__ == Animal.prototypeです)。