檜山正幸のキマイラ飼育記 このページをアンテナに追加 RSSフィード Twitter

キマイラ・サイトは http://www.chimaira.org/です。
トラックバック/コメントは日付を気にせずにどうぞ。
連絡は hiyama{at}chimaira{dot}org へ。
蒸し返し歓迎!
このブログの更新は、Twitterアカウント @m_hiyama で通知されます。
Follow @m_hiyama
ところで、アーカイブってけっこう便利ですよ。

2005-09-08 (木)

プログラマのためのJavaScript (7):プロトタイプ継承の正体

| 13:35 | プログラマのためのJavaScript (7):プロトタイプ継承の正体を含むブックマーク

「JavaScriptには、クラスとその継承はないが、プロトタイプベースの継承をサポートしている」と、まー、これはよく言われる決まり文句です。がしかし、JavaScriptにおけるアレを継承と呼んでいいものかどうか? 「継承」といえば、多くの人が通常の継承、つまりクラスベースの継承をイメージするでしょうし、無意識に通常の継承とのアナロジーを追うことになるでしょう。これは危険だと思いますよ。

「プロトタイプ」とか「継承」という言葉から連想する常識的なイメージは全部捨てて、事実を白紙から眺めましょう。その事実とは、「JavaScriptには、実行時プロパティ検索のメカニズムがある」と、それだけです。

今回の内容:

  1. 「プロトタイプ」じゃ、なんのことだかわからない
  2. __proto__プロパティ、__proto__オブジェクト、__proto__チェーン
  3. __proto__チェーンを追いかけてみる
  4. __proto__チェーンを使ったプロパティ検索
  5. この続きと今回のまとめ

●「プロトタイプ」じゃ、なんのことだかわからない

「プロトタイプ」という言葉から、デザインパターンのPrototypeパターンとか、Selfのようなクラスレス言語のクローニング(オブジェクト複製)を連想する人がいるでしょう。いずれも、オブジェクトの生成に関わる概念です。が、JavaScriptでクローニングを使っているわけではないので、この意味での「プロトタイプ」は忘れましょう!

では、JavaScriptのプロトタイプですが … …、いや、そもそも、JavaScriptの話題に限定してさえ「プロトタイプ」って言葉を使うこと自体がヤバいのだよね、

JavaScriptにおける「プロトタイプ・オブジェクト」って言葉は、まったく異なる2つの意味で使われています。混乱を避けるために、ここでは、「__proto__オブジェクト」と「prototypeオブジェクト」という言葉を使い分けます。この2つは別物です!(後で違いを説明する)

また、安易なアナロジーから「継承」と言うのもやめます(見出しは例外)。代わりに「プロパティ検索」(property lookup; property search)を使います。JavaScriptのプロパティ検索のメカニズムを知った上で、それをどうしても「継承」と呼びたいなら、それはまー勝手ですけど。

●__proto__プロパティ、__proto__オブジェクト、__proto__チェーン

JavaScriptの全てのオブジェクトに、__proto__という(なんだかPythonっぽい)名前のプロパティが必ずあります。ただし、この__proto__プロパティが公開されてない処理系もあるので注意。以下では、__proto__プロパティがユーザー(JavaScriptプログラマ)から見えて触れる前提で話をします。

オブジェクトxの__proto__プロパティは次の2つの性質を持ちます。

  1. その値は、nullであるか、またはxと別なオブジェクトである。
  2. xからはじめて、x.__proto__、x.__proto__.__proto__、x.__proto__.__proto__.__proto__、…… とたどっていくとき、xに戻ってしまうことはない。

xの__proto__プロパティの値(であるオブジェクト)を、xの__proto__オブジェクトと呼び、x, x.__proto__, x.__proto__.__proto__, …… のような列を__proto__チェーンと呼びます。

__proto__チェーンは、__proto__の値にnullが出現したところで終わります。サイクルが生じないことは、上の2番目の性質から保証されます。

●__proto__チェーンを追いかけてみる

次の関数は、__proto__チェーンを配列にして返すものです。

function protoChain(x) {
 var chain = [];
 for (var p = x; p != null; p = p.__proto__) {
  chain.push(p);
 }
 return chain;
}

結果を見やすく表示するために次を定義(だだし、Rhino用):

/* 引数は配列だと仮定 */
function printArray(a) {
 for (var i = 0; i < a.length && i < 10; i++) {
  print(i + ":" + a[i]);
 }
 if (i < a.length) {
  print("... (more)");
 }
} 

さーて、試してみましょう。

js> printArray(protoChain(1))
0:1
1:0
2:[object Object]
js> printArray(protoChain("hello"))
0:hello
1:
2:[object Object]
js> printArray(protoChain([1, 2]))
0:1,2
1:
2:[object Object]
js> printArray(protoChain({}))
0:[object Object]
1:[object Object]
js> printArray(protoChain(Number))
0:function Number() { [native code for Number.Number, arity=1] }

1:function () {
	[native code, arity=0]
}

2:[object Object]
js>

数値1の__proto__オブジェクトは0で、0の__proto__オブジェクトも存在するようですね。プレーンなオブジェクト{}の__proto__チェーンの長さは1(現れるオブジェクト数は2)その他は長さ2になってますが、人為的に長いチェーンを作ることもできます。

js> var a = {}
js> var b = {}
js> var c = {}
js> b.__proto__ = a
[object Object]
js> c.__proto__ = b
[object Object]
js> printArray(protoChain(c))
0:[object Object]
1:[object Object]
2:[object Object]
3:[object Object]
js> 

この例では、「c → b → a → なんか知らないオブジェクト」というチェーンです。

ここで、意地悪をしてサイクルを作ってみましょう。

js> var a = {}
js> var b = {}
js> a.__proto__ = b
[object Object]
js> b.__proto__ = a
js: "<stdin>", line 85: Cyclic __proto__ value not allowed.
js> 

さすがに拒絶されましたね。

●__proto__チェーンを使ったプロパティ検索

さて、今まで調べた__proto__チェーンは何に使われるのでしょうか。その答えは、「プロパティ検索に使われる」です。JavaScript処理系がx.fooという式を見たとき、次のように動作します。

  • オブジェクトxに"foo"という名前のプロパティがあれば、その値を返す。
  • オブジェクトxでプロパティが見つからないとき、xの__proto__オブジェクトの"foo"という名前のプロパティを探し、あれば、その値を返す。
  • 以下同様に、__proto__チェーンをたどってプロパティを探す。チェーンをたどり切ってもなお見つからないなら、undefined値を返す。

実験してみます。

js> var point = {x:20, y:30}
js> point.y
30
js> point.color
js> typeof point.color
undefined
js> var colored = {color:"black"}
js> point.__proto__ = colored
[object Object]
js> point.color
black
js> 

colorというプロパティはオブジェクトpointに定義されていなかったのですが、オブジェクトcoloredを__proto__プロパティに設定することにより、あたかもpointにもcolorプロパティが存在するように振る舞います。

JavaScriptでは、メソッドはプロパティの一種(値が関数オブジェクトであるプロパティ)ですから、メソッド検索もこのメカニズムが使われます。

●この続きと今回のまとめ

これで、__proto__オブジェクトの話は終わりです。prototypeオブジェクトの話はしてません。prototypeオブジェクトは、JavaScriptのオブジェクト生成に関わるので次回で扱うことにします。

  1. JavaScriptには、実行時のプロパティ検索のメカニズムがある。
  2. どんなオブジェクトにも、__proto__という名のプロパティがある。
  3. __proto__プロパティの値(のオブジェクト)をたどっていくとチェーンができる。これを__proto__チェーンと呼ぶ。
  4. オブジェクトのプロパティ値を求めるとき、__proto__チェーンをたどってプロパティを探す。
  5. メソッドもプロパティの一種なので、__proto__チェーンをたどって検索される。