継承ってなんだろう? (1)

最初に注意

自分はつい最近JavaScriptをかじったばかりの,ただの初級者です.以下の文章は単に自分はこう考えている,というだけで,誤りが含まれている可能性は十分にあります.もし読まれる方がいたら,その点を十分にご理解ください.また誤りの指摘は大歓迎です.(以上、コピペ)

ObjectとPointの違い

前回,pointオブジェクトを2通りの方法で定義してみました.1つはObjectコンストラクタを使って空のオブジェクトを作成し,プロパティを後から追加する方法.もう1つはPointコンストラクタを定義し,Pointコンストラクタのなかでプロパティを設定するコードを記述,利用する側はPointコンストラクタ関数をnew演算子と共に呼び出すというものでした.

var obj = new Object();
obj.x = 1;
obj.y = 2;
obj.toString = function(){ return "(" + this.x + "," + this.y + ")" ; };
function Point(x, y)
{
  this.x = x;
  this.y = y;
  this.toString = function(){ return "(" + x + "," + y + ")" ; };
}
var point = new Point(0,0);

Pointコンストラクタを使用する場合でも,内部的にはObjectコンストラクタが呼ばれているという説明をしました.なので,どちらを使用しても最終的に得られるオブジェクトは一緒になります.これは例えば以下のコードで確かめられます.

for (prop in point)
{
  document.write("name:=" + prop + "," + "value:=" + point[prop], "<br />");
}
for (prop2 in obj)
{
  document.write("name:=" + prop2 + "," + "value:=" + obj[prop2], "<br />");
}
// 実行結果(FireFox 2.0で確認)
// ----------------------------------------------------
// name:=x,value:=0
// name:=y,value:=0
// name:=toString,value:=function () { return "(" + x + "," + y + ")"; }
// name:=x,value:=1
// name:=y,value:=2
// name:=toString,value:=function () { return "(" + this.x + "," + this.y + ")"; }

さて,これだけ一緒だといっておいてなんですが,実はこの2つは同一ではありません.ここからその違いについて説明します.まずは以下のコードを見てください.

document.write(point.__proto__.constructor, "<br />");
document.write(obj.__proto__.constructor, "<br />");

// 実行結果(FireFox 2.0で確認)
// ----------------------------------------------------
// function Point(x, y) { this.x = x; this.y = y; this.toString = function () {return "(" + x + "," + y + ")";}; }
// function Object() { [native code] }

プロトタイプチェーンによるプロパティ検索

さて,__proto__ってなんだ?って話の前に注意,__proto__というのはFireFoxでのみ使用できるオブジェクトってことになっています.そもそもInternet ExplorerでサポートされているJavaScriptのバージョンは1.3でFireFox(たしか,1.7)と比べてバージョンに開きがあるんですけど,さらに__proto__はFF独自プロパティらしいんですよね.JavaScript 2.0ではこういった部分が言語仕様として定義されるといいなと思います.ホントに.
注意が終わったところで本題です.JavaScript第3版の205ページから引用します.

どのオブジェクトにも,プロパティの継承元になるオブジェクト(もしあれば)を参照する内部プロパティがあります.コンストラクタでオブジェクトを生成すると,そのコンストラクタ専用のprototypeプロパティ(内部オブジェクトではない)の値が内部プロパティに設定されます.

内部プロパティといっているのは,FireFox(原文ではNetscape Navigatorになっているけど)における__proto__のことですね.IEでもおそらく,__proto__とは別の名前で,非公開になっていますが同等のプロパティがあるのだと思います.ここでは__proto__で統一します.この__proto__もオブジェクトですから,やはり__proto__プロパティを持っているはずです.そして,__proto__.__proto__もオブジェクトだから……と際限なく続きます.これをプロトタイプチェーンと呼びます.
なお,際限なくというのは誇張で,実は終わりがあります.Objectコンストラクタ関数によって作成されたオブジェクトは__proto__を持ちますが,__proto__.__proto__はnullになります.nullの__proto__というのはないので,ここが終点です.
__proto__プロパティの役割はなんでしょう?前回,JavaScriptにおけるオブジェクトとは連想配列だと説明しました.これはある意味正しいのだけど,少し簡略化しすぎています.たしかにオブジェクト自身は連想配列を持ちますが,__proto__オブジェクトも連想配列を持ちます(__proto__もオブジェクトだから).もし,obj.propというコードがあったとき,まずは自分自身の連想配列からpropという名前をKeyにもつプロパティを検索しますが,なければ__proto__の連想配列から検索します.さらになければその上に遡ります.これがJavaScriptにおけるプロパティ検索の仕組みです.__proto__プロパティは,このプロパティ検索の仕組みの根幹をなすものです.
さて,先ほどの引用文にはprototypeプロパティという言葉もありました.これは関数オブジェクトに必ず存在するものです.もし

var obj = new Object();

というコードがあれば,オブジェクトを作成する過程で以下の処理が行なわれているはずです.

obj.__proto__ = Object.prototype;

そして,Object.prototype.__proto__はnullです.
Objectオブジェクトには組み込みのプロパティがいくつか用意されています.例えば,toStringとかvalueOfなどです.これらは実際にはObject.prototypeに定義されています.私たちはこれをプロパティ検索の仕組みにより利用出来るわけです.
Pointオブジェクトではどうでしょうか?ここで,前回等価だと説明したコードを見てみます.

// var point = new Point(1,2); は,以下と等価

var point = new Object();
Point.apply(point,[1,2]);
point.__proto__ = Point.prototype;

point.__proto__ はPoint.prototypeが設定されます.point.__proto__.__proto__には,Object.prototypeが設定されます.これはなぜかというと,PrototypeオブジェクトはObjectコンストラクタによって生成される,というルールがあるからです.そして,point.__proto__.__proto__.__proto__にはnullが入って,これで連鎖が終了です.
Object.propertyにはObjectオブジェクトの組み込みプロパティを定義していました.これと全く同じことがPoint.prototypeでも可能です.ちょっと説明が長くなりすぎたので,続きの説明は次回行ないます.