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

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

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

参照用 記事

プログラマのためのJavaScript (4):オブジェクト構造

前回の最後で、「次回はオブジェクト構造についてでも。」と予告したので、それに従います。

●オブジェクトグラフ

JavaScripのオブジェクトは情報や機能性を持っていますが、それらの情報/機能性は、必ず名前を経由してアクセスされます。ここで名前とはプロパティ名です。オブジェクト、プロパティ名、プロパティ値の関連を示す絵を描きたいのですが、僕は絵が苦手だし(手描きはけっこううまいよ→自画像)、画像アップロードをしたこともなくて…… でも大丈夫。RDFグラフの絵が代用になります。RDFグラフは、W3C神崎さんのサイトにイッパイありますね。

例えば、コレ。Subject, Predicate, Objectを、それぞれ「もとになる(基準)オブジェクト」、「プロパティ名」、「プロパティ値のオブジェクト」に置き換えてください。

もうちょっと複雑でキレイなコレならば、まるいのがオブジェクトで、黄色い四角は基本データ(numberやstring)と考えればいいでしょう。

RDFと同様に、JavaScriptのオブジェクト群もグラフを構成します(オブジェクトグラフと呼びましょう)。グラフのノードがオブジェクト(基本型データも含む)で、グラフの辺は参照です。そして、辺にはプロパティ名でラベルが付けられます。辺には必ず名前(ラベル)が付きますが、ノードには名前がないことに注意。ノードに名前が直接へばりついていると思うのは誤解のもとです(気を付けましょう)。

●プロパティを列挙してみる

JavaScriptプログラムからは、必ず大域オブジェクトが暗黙的に見えています。これは、単にfooと名前を書けば、それは大域オブジェクトの“fooという名前のプロパティ”を意味する、ということ。大域オブジェクト自体は無名ですが、ブラウザ環境ならwindowやselfという名前(大域オブジェクトのプロパティ名)が自分自身(つまり大域オブジェクト)を指しています。ブラウザ以外でも、キーワードthisをトップレベルで使えば大域オブジェクトを指せます。

さて、オブジェクトobjのプロパティ名を列挙するには、for (var p in obj) というfor/in構文が使えます。例えば、Rhinoの対話的環境でプロパティ名を書き出すにはfor (var p in this) {print(p);} と入力すればOK。ただし、Rhinoでは面白い結果は得られません。ブラウザなら多くの大域プロパティを持っているので、次のコードを試してみてください。


<html>
<head>
<title>Enumerate Global Properties</title>
<script>
function addItem(x) {
// This function uses DOM API calls.
var textNode = document.createTextNode(x);
var item = document.createElement("li");
item.appendChild(textNode);
var list = document.getElementById("list");
list.appendChild(item);
}
function enumProps() {
try {
for (var p in this) {
addItem(p);
}
} catch (e) {
alert("Sorry, something wrong.");
}
}
</script>
</head>
<body>

<p><button onclick="enumProps()">Enum Props</button></p>
<ol id="list"></ol>

</body>
</html>


[サンプルへのリンク]

このコードの本質的な部分は for (var p in this){addItem(p);} だけ。変数pに入るのはプロパティ名(の文字列)であることに注意。つまり、これは文字列のリストに対する繰り返しになります。

再帰的に列挙、いや、ちょっと待て

for/inですべてのプロパティを列挙できるわけではありません。例えば、コア言語仕様で規定された標準組み込みオブジェクト(を指すプロパティ)は見えません。特定実行環境がサポートしている非標準プロパティは見えることが多いでしょう。for/inに引っかからないプロパティに関しては、前もってその名前を知っておかなくてはならないのですね。

for/inで列挙できない既定義プロパティは、どちらかというとシステム寄りで、これをたどるとシステムのメカニズムが透けて見えて面白いのですが、そのハナシは別な機会にして、for/inによる列挙についてもう少し; 先に出したブラウザ大域プロパティ一覧は、「アルファベット順にソートしないと気持ち悪い」とか「名前だけでなくデータ型も一緒に出たほうがいい」とかのツッコミはあるでしょうが、なんといっても1レベルしか表示しないのがもの足りません。

ツリー構造を扱う定石に従い、再帰的にたどってみましょうか。いやっ、ダメです。オブジェクト達はオブジェクトツリーを形成するのではなくて非ツリー状のグラフになっています。サイクル(循環)が存在するのです。サイクルの典型例は、お馴染みのwindowプロパティです。サイクルを持つグラフ上で再帰をやると無限ループ(つうよりスタックあふれ)に陥<おちい>ります。

とりあえず、レベルを制限して打ち切ってしまうコード例が以下です(不満が残る処置ですけどね)。定数(のつもり)のMAX_LEVELとMAX_WIDTHを書き変えて試すといいでしょう。現状、id:m-hiyama:20050812:1123811975に報告したような問題があるので、これを手直してしても完全に動くようにはなりません、残念。


<html>
<head>
<title>Enumerate Properties Recursively (incomplete)</title>
<script>
var MAX_LEVEL = 3;
var MAX_WIDTH = 6;

var _level = 0;

function addText(element, text) {
var textNode = document.createTextNode(text);
element.appendChild(textNode);
}
function addNewContainer(parentContainer, text_) {
var div = document.createElement("div");
div.className = "container";
if (typeof text_ != 'undefined' && text_ != null) {
addText(div, text_);
}
parentContainer.appendChild(div);
return div;
}

function enumPropsRec(obj, parentContainer) {
if (_level >= MAX_LEVEL) return false;
_level++;
var newContainer, v;
var i = 0;
var breaked = false;
for (var p in obj) {
if (i++ >= MAX_WIDTH) {
breaked = true;
break;
}
newContainer = addNewContainer(parentContainer, p);
v = obj[p];
if (typeof v == 'object' && v != null) {
if (!enumPropsRec(v, newContainer)) {
addText(newContainer, " -*");
}
}
}
if (breaked) addNewContainer(parentContainer, "* * * * *");
_level--;
return true;
}

function enumProps() {
try{
var root = document.getElementById("root");
enumPropsRec(this, root);
} catch(e) {
alert("Sorry, something wrong.");
}
}
</script>
<style>
#root {margin-left:0px; border: solid 2px black;}
.container {margin-left:1em; border:solid 1px gray;}
</style>
</head>
<body>

<p><button onclick="enumProps()">Enum Props</button></p>
<div id="root">Global Object (no name)</div>

</body>
</html>

[サンプルへのリンク]

●今回のまとめ

  1. オブジェクトが持つ情報や機能性には、必ずプロパティ名を経由してアクセスする。
  2. JavaScriptのオブジェクト群は、RDFグラフと似たグラフとみなせる。プロパティ名は辺のラベルである。
  3. JavaScriptプログラムからは、無名の大域オブジェクトが必ず見えている。
  4. プロパティの列挙にはfor/in構文が使える。が、これで列挙されるのはプロパティ名文字列である。
  5. 全てのプロパティをfor/inで列挙できるわけではない。
  6. オブジェクトグラフはたいていサイクル(循環)を含む。

1つのオブジェクトを個体、複数のオブジェクト達の編成を集団と考えると、個と集団が絡み合うところに、JavaScriptオブジェクト構造があるのですね。

次回は、…… まだ決めてません。