プロトタイプチェーン

  • 22
    いいね
  • 0
    コメント
この記事は最終更新日から1年以上が経過しています。

このエントリは、プロトタイプチェーンについてまとめたメモです。
コード関連は、Node.js の REPL で確認してます。

prototype プロパティ

全ての関数は prototype という名前のプロパティを持ちます。中身は Object です。

function A() {}
console.log(A.prototype);       // {} と表示

Object なので変数や関数の追加ができます。

function B() {}
B.prototype.a = 123;
B.prototype.b = 'bbb';
B.prototype.c = function() {
    console.log('B.prototype.c');
}
console.log(B.prototype);       // { a: 123, b: 'bbb', c: [Function] }

この prototype を使用すると継承を行うことができます。

function C() {}
C.prototype.show = function() {
    console.log('C.prototype.show');
}
var c1 = new C();
c1.show();        // 'C.prototype.show' と表示

new でオブジェクトを生成した後に関数を追加したり変更しても反映されます。

function D() {}
D.prototype.show = function() {
    console.log('D.prototype.show');
}
var d1 = new D();

// d1オブジェクトを生成後にshow2関数を追加する
D.prototype.show2 = function() {
    console.log('D.prototype.show2');
}
d1.show();        // 'D.prototype.show' と表示
d1.show2();       // 'D.prototype.show2' と表示

// show関数の内容を変更する
D.prototype.show = function() {
    console.log('D.prototype.show - changed!');
}
d1.show();        // 'D.prototype.show - changed!' と表示

__proto__ プロパティ

__proto__ プロパティは全てのオブジェクトが持つプロパティで、オブジェクト生成に使ったコンストラクタの prototype プロパティへの参照です。

// ビルトインオブジェクト
var o = {};
console.log(o.__proto__);                         // {}
console.log(o.__proto__ === Object.prototype);    // Objectのprototype
var a = [];
console.log(a.__proto__);                         // []
console.log(a.__proto__ === Array.prototype);     // Arrayのprototype
var s = "123";
console.log(s.__proto__);                         // ''
console.log(s.__proto__ === String.prototype);    // Stringのprototype
// 独自関数
function E() {}
E.prototype.show = function() {}
var e1 = new E();
console.log(e1.__proto__);                        // { show: [Function] }
console.log(e1.__proto__ === E.prototype);        // E関数のprototype

オブジェクトのプロパティを探す場合、次の順番で探します。
1. オブジェクト自身のプロパティ
2. __proto__ のプロパティ(継承元の prototype)
3. 2 の __proto__ のプロパティ(継承元の prototype の __proto__)
4. __proto__ が null になるまで 2, 3 を繰り返す(Object.prototype の __proto__ は null )

このようにプロパティを探すために __proto__ を辿る連鎖をプロパティチェーンと呼びます。

もう少し詳しく見ていきます。
次の例は、G → H → Object と継承した場合です。

var H = function() {};
H.prototype.show1 = function() {
    console.log('H.prototype.show');
}
// H を継承して G を作成
var G = function() {};
G.prototype = Object.create(H.prototype);    // 継承元の prototype を指定
G.prototype.constructor = G;                 // Object.createはconstructorを変更しないため
G.prototype.show2 = function() {
    console.log('G.prototype.show');
}
var hh = new H();
var gg = new G();

// 検証

// H オブジェクト
console.log(H.prototype.__proto__ === Object.prototype);              // true

// G オブジェクト
console.log(G.prototype.__proto__ === H.prototype);                   // true
console.log(G.prototype.__proto__.__proto__ === Object.prototype);    // true

// ggオブジェクト
console.log(gg.__proto__ === G.prototype);                            // true
console.log(gg.__proto__.__proto__ === H.prototype);                  // true
console.log(gg.__proto__.__proto__.__proto__ === Object.prototype);   // true
console.log(gg.__proto__.__proto__.__proto__.__proto__ === null);     // true


// 実行
hh.show1();        // 'H.prototype.show' と表示
gg.show1();        // 'H.prototype.show' と表示
gg.show2();        // 'G.prototype.show' と表示
hh.show2();        // TypeError: hh.show2 is not a function

この G と H のコードが実行された後のオブジェクトの様子をJSON形式のイメージで表します。

gg : {
    __proto__ : G.prototype を参照
}
hh : {
    __proto__ : H.prototype を参照
}
G : {
    __proto__ : Function.prototype を参照
    prototype : {
        constructor : G,
        __proto__ : H.prototype を参照
    }
}
H : {
    __proto__ : Function.prototype を参照
    prototype : {
        constructor : H,
        __proto__ : Object.prototype を参照
    }
}
Object : {
    __proto__ : Function.prototype を参照
    prototype : {
        constructor : Object,
        __proto__ : null
    }
}
Function : {
    __proto__ : Function.prototype を参照
    prototype : {
        constructor : Function,
        __proto__ : Object.prototype を参照
    }
}

ggオブジェクトが show1 プロパティを探す場合は、
1. gg の show1 プロパティを探す。無いので 2へ
2. gg の __proto__ (G.prototype)で show1 プロパティを探す。無いので 3へ
3. gg の __proto__ の __proto__ (H.prototype)で show1 プロパティを探す。あったのでこれを使用する。

ということを行っています。

hh オブジェクトが show2 プロパティを探す場合は、
1. hh の show2 プロパティを探す。無いので 2へ
2. hh の __proto__ (H.prototype)で show2 プロパティを探す。無いので 3へ
3. hh の __proto__ の __proto__ (Object.prototype)で show2 プロパティを探す。無いので 4へ。
4. hh の __proto__ の __proto__ の __proto__ (Object.prototype.proto)は null なので探索終了。なかったのでエラー。

となります。

参考

http://maeharin.hatenablog.com/entry/20130215/javascript_prototype_chain
http://d.hatena.ne.jp/m-hiyama/20050908/1126154117
http://qiita.com/LightSpeedC/items/d307d809ecf2710bd957
http://d.hatena.ne.jp/m-hiyama/20051017/1129510043
https://gist.github.com/y-yu/3301790