Javascriptのオブジェクトの基本:プロトタイプチェーン

正直に言うと、現在のJavascriptECMAScript)は、関数系言語だが、オブジェクト指向言語だかよく分からない。

クロージャーのような機構は関数系言語に特徴的だと思うし、基本はそうなのだと思う。紛らわしいのは、javascriptの文脈に「オブジェクト」という言葉が頻発すること、new演算子からインスタンス生成を行うことなど、オブジェクト指向言語的な特徴を持っていること。

(こんな議論自体に全く意味はないのだろうが)javascriptのオブジェクトが、本質的にハッシュテーブルであると考えると「プロパティーとそれに関する手続きを一緒にしたもの」という「オブジェクトの一般定義」にそぐわない気がしなくもない(だけど、関数オブジェクトは合致しそうだな。。。)。

このログでは、オブジェクト指向言語に特徴的な、オブジェクトの「拡張(extends)」について見てみる。

先のログ『Javascriptのオブジェクトの基本:オブジェクトの動的な変更』では、オブジェクトのインスタンス化と、その動的変更を検証した。また、『Javascriptのオブジェクトの基本:インスタンス化とthis』では、(javaのように)「コンクリートに定義されているように見えるオブジェクトも、実行局面では、動的な変更と同等(=2つの間に本質的な違いはない)」と結論した。

この「インスタンスの動的変更」は、オブジェクトのプロパティーの置換によるメソッドの上書き(オーバーライド)の一種と見なすことができる。

この他に、javasciptには「プロトタイプチェーン(prototype chain)」というオブジェクトの拡張機構がある。

この機構は、

関数オブジェクトに雛形(プロトタイプ: prototype)を設定して、関数オブジェクトから雛形への参照ルートを作る

もの。「雛形を儲けてそれを参照できるようにする」ということは、javaでいうところの「サブクラスからスーパークラスを参照する」ことと(基本コンセプトは)同等であるから、オブジェクトの拡張機構とみることができる。

サンプルを見たほうが早い。
この例では、testFunc2()((5)式)は空っぽの関数オブジェクトとして定義される。このtestFunc2に雛形を設定するのが(6)式である。ここまでが、testFunc2()の定義式で、(7)でインスタンス化した際には、obj2は、雛形のobj1を暗黙裡に参照するようになる。
(8)式で、本来、testFunc2()にないローカル変数(プロパティー)p1とp2を参照すると、暗黙裡にobj1のp1とp2を参照して、p1は10、p2はhogehogeを返す。

function testFunc1() { // --- (1)
    this.p1 = 10;  // --- (2)
    this.p2 ='hoge'; // --- (3)
}

var obj1 = new testFunc1(); // --- (4)

function testFunc2() { // --- (5)
}

testFunc2.prototype = obj1; // --- (6) prototypeにobj1を設定する。

var obj2 = new testFunc2(); // --- (7) obj2は、obj1を暗黙参照する「同一の性質のオブジェクト」。

alert('obj2.p1: '+obj2.p1+', obj2.p2: '+obj2.p2); // --- (8) obj2はobj1を暗黙参照する。


この雛形の設定は何階層にも渡ってできるので、プロトタイプチェーンと呼ばれる。
以下に、上より少しだけ込み入ったサンプルを示す。

この例では、testFunc1()、testFunc2()、testFunc3()の3つの関数オブジェクトを用意して、以下の手順でプロトタイプチェーンの検証を行う。

  1. 全ての関数オブジェクトのプロトタイプは、testFunc1()。(1)式
  2. testFunc2()は空の関数オブジェクトで、testFunc1()のインスタンスobj1をプロトタイプとする。(4)式。
  3. testFunc2()のインスタンスobj2にp2を動的追加する。(5)式
  4. testFunc3()は空の関数オブジェクトで、obj2をプロトタイプとする。(8)式。
  5. testFunc3()のインスタンスobj3のプロパティーp1、p2を参照して、結果を調べる。

検証したいことは、

  • obj3.p1については、obj2にそれが存在しないので、obj1.p1を参照する
  • obj3.p2については、obj2にそれが存在するので、obj2.p2を参照する

、すなわち、obj3のプロパティーそれぞれに対してプロトタイプへの参照が行われる、ということである。

function testFunc1() { // ---(1)
    this.p1 = 10;
    this.p2 ='hoge';
}

var obj1 = new testFunc1(); // ---(2)

function testFunc2() { // --- (3)
}
testFunc2.prototype = obj1; // ---(4) prototypeオブジェクトにobj1を設定する。

var obj2 = new testFunc2(); // --- (5)
obj2.p2 = 'hogehoge'; // ---(6) obj2にp2を動的追加し、hogehogeとする。

function testFunc3() { // ---(7)
}
testFunc3.prototype = obj2; // ---(8) prototypeオブジェクトにobj2を設定する。
var obj3 = new testFunc3(); // ---(9)

alert('obj3.p1: '+obj3.p1+', obj3.p2: '+obj3.p2); // ---(10)

結果、目論見通り、「obj3.p1はobj1.p1を参照」して10がアラートされ、「obj3.p2はobj2.p2を参照」してhogehogeがアラートされる。(10)式。

以下は、上から、obj1,obj2,obj3をprettyPrint.jsでダンプしたものである。