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 で述べるオリジナルのObject
prototype
オブジェクトに設定する。F
の[[Call]]
プロパティを呼び出す。Result(1) をthis
値として提供し、引数値として[[Construct]]
に渡された引数リストを提供する。- Type(Result(6)) が Object ならば、Result(6) を返す。
- Result(1) を返す。