JavaScriptのプロトタイプチェインについての、自分用の覚書です。殴り書きなのでくどいし推敲もしていません。用語も少し変かも。コンストラクタが生成したオブジェクトを便宜上インスタンスと書いたりその他色々。
function C(){} // コンストラクタ
function CC(){} // コンストラクタ
CC.prototype = new C;
var cc = new CC;
ここで、CCのインスタンスccのmプロパティ、cc.mを参照すると何が起こるだろうか。
まず、cc.mは存在しないから、ccの内部プロパティ[[Prototype]]からmを探す。これをcc.__proto__とすれば、即ちcc.__proto__.mを探す。但し実際にはcc.__proto__は不可視なので注意。
ここで、cc.__proto__には、インスタンスであるccがnewで生成されるその時、コンストラクタCCのprototypeプロパティが参照しているオブジェクト(この例ではCのインスタンスになっている)がセットされる。従って、cc.__proto__.mは、Cのインスタンスのmプロパティを参照することになる。
これも存在しないので、今度はcc.__proto__の[[Prototype]]内部プロパティからmを探す。cc.__proto__.__proto__と表記しよう。
ここで、cc.__proto__はCのインスタンスであった。従って、cc.__proto__.__proto__は「new C」された時点におけるコンストラクタCのprototypeプロパティが参照しているオブジェクトである。従って:
CC.prototype = new C;
が評価された以降に、C.prototypeが書き換えられていない限りにおいて、cc.__proto__.__proto__.m はC.prototype.mでアクセスすることが出来る。
以上より、各コンストラクタのprototypeプロパティが書き換えられていない限りにおいて、次のようにプロパティの検索が行われる。
cc.m
→ cc.__proto__.m [= CC.prototype.m]
→ (new C).__proto__.m [= C.prototype.m]
__proto__は[[Prototype]]内部プロパティ(不可視)である。cc.__proto__は、newによってccが生成された時点においてCC.prototypeと同じオブジェクトを参照している。全く同じことだが (new C).__proto__もその時点においてC.prototypeと同じオブジェクトを参照している。プロトタイプチェインによるプロパティの検索には、不可視の__proto__が参照されるが、コンストラクタのprototypeプロパティが参照されるわけではない。コンストラクタのprototypeプロパティが参照されるのは、インスタンスが生成される際だけである。
ダラダラ書いたけれど、ポイントは二つか。
[[Prototype]]プロパティが経由される(不可視)。コンストラクタのprototypeプロパティではない。
prototypeプロパティが参照されるのは、インスタンスが生成される時だけである。この時、インスタンスの[[Prototype]]プロパティの参照先が、コンストラクタのprototypeプロパティの参照先と同一化される。
確かめる。
function C(){}
var c = new C;
C.prototype = {x: 'x'};
c.x; // undefined
function C(){}
C.prototype = {x: 'x'};
var c = new C;
c.x; // 'x'
自明。コンストラクタのprototypeプロパティ自身を書き換えることの危険性というかナンセンスさというか、その辺が示唆されている。次、プロトタイプチェインを繋げたつもりが繋がっていない例:
function C(data){
this.data = data;
}
C.prototype.getData = function(){return this.data};
function CC(data){
this.data = data;
}
CC.prototype.prototype = C.prototype;
var cc = new CC;
cc.getData // undefined
cc.getDataのプロトタイプチェインを辿ってみる。
cc.getData(見つからない)
cc.__proto__.getData = CC.prototype.getData (見つからない)
CC.prototype.__proto__.getData = Object.prototype.getData (見つからない)
Object.prototype.__proto__の値はnullであるから、プロトタイプチェイン終了、undefinedが返却される
書き方を変えると、cc.getDataはこの場合、最終的にcc.__proto__.__proto__.__proto__までアクセスしてnullで終わってundefinedが返却される。くどいけれど実際にはCC.prototypeは参照しない点に注意。この場合たまたま
cc.__proto__とCC.prototypeの参照先が同じだから便宜上等号で結んでいる。
cc.__proto__.__proto__.__proto__
= CC.prototype.__proto__.__proto__
= Object.prototype.__proto__
= null
__proto__で表記している[[Prototype]]内部プロパティは、デフォルトではObject.prototypeへの参照になっている点に注意。newする際に[[Prototype]]内部プロパティがコンストラクタのそれの参照先に変更されると考えてよいだろう。そんなわけで:
CC.prototype.prototype = C.prototype;
ではなくて:
CC.prototype.__proto__ = C.prototype;
としたいわけだが、__proto__は不可視であってnewでインスタンスを生成する際(正確にはコンストラクタ関数の[[Construct]]内部プロパティが呼び出される際)以外には参照先を変更することは出来ない。
チェックポイント:
__proto__で表記してきた[[Prototype]]内部プロパティは、デフォルトでObject.prototypeオブジェクトを参照している。
Object.prototypeの[[Prototype]]内部プロパティ(Object.prototype.__proto__)のみ、デフォルトで値がnullである(変更不可)。
典拠として、ECMAScript262の邦訳を参照できる。
Oの[[Get]]メソッドがプロパティ名Pで呼出されると、次のステップがとられる
OがPという名前のプロパティを持っていなければ、ステップ 4 へ進む。- そのプロパティの値を取得する。
- Result(2) を返す。
Oの[[Prototype]]がnullならば、undefinedを返す。[[Prototype]]の[[Get]]メソッドを、プロパティ名Pで呼び出す。- Result(5) を返す。
FunctionオブジェクトFの[[Construct]]プロパティが呼出されるとき、次のステップが取られる:
- 新しい Native ECMAScript オブジェクトを生成する。
- Result(1) の
[[Class]]プロパティを"Object"に設定する。Fのprototypeプロパティの値を取得する。- Result(3) がオブジェクトならば、Result(1) の
[[Prototype]]プロパティを Result(3) に設定する。- Result(3) がオブジェクトでなければ、Result(1) の
[[Prototype]]プロパティを、セクション 15.2.3.1 で述べるオリジナルのObjectprototypeオブジェクトに設定する。Fの[[Call]]プロパティを呼び出す。Result(1) をthis値として提供し、引数値として[[Construct]]に渡された引数リストを提供する。- Type(Result(6)) が Object ならば、Result(6) を返す。
- Result(1) を返す。