このブログの更新は Twitterアカウント @m_hiyama で通知されます。
Follow @m_hiyama

メールでのご連絡は hiyama{at}chimaira{dot}org まで。

はじめてのメールはスパムと判定されることがあります。最初は、信頼されているドメインから差し障りのない文面を送っていただけると、スパムと判定されにくいと思います。

[参照用 記事]

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

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

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

今回の内容:

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

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

「プロトタイプ」という言葉から、デザインパターンの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__チェーンをたどって検索される。