Javascriptのオブジェクトの基本:プロトタイプチェーン
正直に言うと、現在のJavascript(ECMAScript)は、関数系言語だが、オブジェクト指向言語だかよく分からない。
クロージャーのような機構は関数系言語に特徴的だと思うし、基本はそうなのだと思う。紛らわしいのは、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つの関数オブジェクトを用意して、以下の手順でプロトタイプチェーンの検証を行う。
- 全ての関数オブジェクトのプロトタイプは、testFunc1()。(1)式
- testFunc2()は空の関数オブジェクトで、testFunc1()のインスタンスobj1をプロトタイプとする。(4)式。
- testFunc2()のインスタンスobj2にp2を動的追加する。(5)式
- testFunc3()は空の関数オブジェクトで、obj2をプロトタイプとする。(8)式。
- 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でダンプしたものである。