Jintrick.netagenda2004年03月アーカイブ → 2004年03月20日

プロトタイプチェインについての覚書(ECMAScript, JavaScript)

JavaScriptのプロトタイプチェインについての、自分用の覚書です。殴り書きなのでくどいし推敲もしていません。用語も少し変かも。コンストラクタが生成したオブジェクトを便宜上インスタンスと書いたりその他色々。

function C(){} // コンストラクタ

function CC(){} // コンストラクタ

CC.prototype = new C;

var cc = new CC;

ここで、CCのインスタンスccmプロパティ、cc.mを参照すると何が起こるだろうか。

まず、cc.mは存在しないから、ccの内部プロパティ[[Prototype]]からmを探す。これをcc.__proto__とすれば、即ちcc.__proto__.mを探す。但し実際にはcc.__proto__は不可視なので注意。

ここで、cc.__proto__には、インスタンスであるccnewで生成されるその時、コンストラクタCCprototypeプロパティが参照しているオブジェクト(この例ではCのインスタンスになっている)がセットされる。従って、cc.__proto__.mは、Cのインスタンスのmプロパティを参照することになる。

これも存在しないので、今度はcc.__proto__[[Prototype]]内部プロパティからmを探す。cc.__proto__.__proto__と表記しよう。

ここで、cc.__proto__Cのインスタンスであった。従って、cc.__proto__.__proto__は「new C」された時点におけるコンストラクタCprototypeプロパティが参照しているオブジェクトである。従って:

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プロパティが参照されるのは、インスタンスが生成される際だけである。

ダラダラ書いたけれど、ポイントは二つか。

確かめる。

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のプロトタイプチェインを辿ってみる。

  1. cc.getData(見つからない)
  2. cc.__proto__.getDataCC.prototype.getData (見つからない)
  3. CC.prototype.__proto__.getDataObject.prototype.getData (見つからない)
  4. 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]]内部プロパティが呼び出される際)以外には参照先を変更することは出来ない。

チェックポイント:

典拠として、ECMAScript262の邦訳を参照できる。

O[[Get]] メソッドがプロパティ名 P で呼出されると、次のステップがとられる

  1. OP という名前のプロパティを持っていなければ、ステップ 4 へ進む。
  2. そのプロパティの値を取得する。
  3. Result(2) を返す。
  4. O[[Prototype]]null ならば、undefined を返す。
  5. [[Prototype]][[Get]] メソッドを、プロパティ名 P で呼び出す。
  6. Result(5) を返す。

8.6.2.1 [[Get]] (P) より

Function オブジェクト F[[Construct]] プロパティが呼出されるとき、次のステップが取られる:

  1. 新しい Native ECMAScript オブジェクトを生成する。
  2. Result(1) の [[Class]] プロパティを "Object" に設定する。
  3. Fprototype プロパティの値を取得する。
  4. Result(3) がオブジェクトならば、Result(1) の [[Prototype]] プロパティを Result(3) に設定する。
  5. Result(3) がオブジェクトでなければ、Result(1) の [[Prototype]] プロパティを、セクション 15.2.3.1 で述べるオリジナルの Object prototype オブジェクトに設定する。
  6. F[[Call]] プロパティを呼び出す。Result(1) を this 値として提供し、引数値として [[Construct]] に渡された引数リストを提供する。
  7. Type(Result(6)) が Object ならば、Result(6) を返す。
  8. Result(1) を返す。

13.2.2 [[Construct]] より


webmaster@jintrick.net
公開: 2004年03月20日
カテゴリ: Javascript