Objectオブジェクト

前回に引き続き、prototype.jsを51行目から見ていこう。

51 Object.extend(Object, {
52   inspect: function(object) {
53     try {
54       if (object === undefined) return 'undefined';
55       if (object === null) return 'null';
56       return object.inspect ? object.inspect() : object.toString();
57     } catch (e) {
58       if (e instanceof RangeError) return '...';
59       throw e;
60     }
61   },
     : (中略)
97   clone: function(object) {
98     return Object.extend({}, object);
99   }
00 });

ここでは、(前回の記事で紹介した)44行目で定義されたObject.extendを使用して、Objectオブジェクトそのものを拡張している。

それでは、ここでの記法と、拡張内容について確認していこう。extendの第一引数(destination)として、プリミティブなコアオブジェクトである、Objectオブジェクトが指定されている。一方、第二引数(source)では、中括弧によるオブジェクト定義の記法により、(inspect, toJSON, keysなどのプロパティを持った)無名オブジェクトが定義され、これがそのまま指定されていることがわかる(inspect/clone以外の定義は掲載を省略している)。

不可解な書き方に見えるかもしれないが、Object.extendの実装を思い出してほしい。Object.extendは、第二引数(source)に指定したオブジェクトが持つプロパティを、第一引数(destination)に指定したオブジェクトにコピーするというものだ。

以上から、ここ(51行目から100行目)では、Objectオブジェクトに(inspect, toJSON, keys, values, cloneの)プロパティを追加定義している、と読むことができる。

なお、ここで挙げたinspectメソッドは、指定されたオブジェクトの文字列表現を返すメソッドだ。ここでは、対象のオブジェクトで定義されているinspectプロパティを返す、もしくは、(inspectプロパティが定義されていない場合、)Object.toString()の結果を返すようになっていることが読み取れる。prototype.js中で定義されているいくつかのオブジェクトでは、inspectプロパティが定義されている。このため、prototype.jsを利用する際には、inspectプロパティを使用することにより、適切な形式での文字列表現を取得できるようになっている。

これをうまく活用すれば、自身で作成したオブジェクトにinspectプロパティを定義しておくことで、Object.inspectという画一的な手段で、より適切な文字列表現を返すことができる、ということになる。

後者のcloneでは、destinationオブジェクトとして、中括弧のみ({ })の空オブジェクトが指定されている。これは、その名のとおり、オブジェクトのクローンを得るために使用するものだ。

では、次のコマンドをprototype.js読み込み後のFirebugコンソールで実行し、Object.inspectとObject.cloneの動作を確認してみよう。(前回からの続きとなっているので注意)

>>> Object.inspect(Dog)
"function () { this.initialize.apply(this, arguments); }"
>>> Object.inspect(d)
"[object Object]"
>>> var e = Object.clone(d)
>>> e.name
"犬B"
>>> e.bark()

興味がある読者は、ここで省略したtoJSON, keys, values などのプロパティについても読み解いてみてほしい。

ところで、Object.extendは、ここで見てきたように、「既に定義されているオブジェクトに対し、プロパティを追加する(機能を拡張する)」という目的で使用されることが多い。prototype.jsを読み進めていくと、NumberやStringなどの定義済みコアオブジェクトを、Object.extendにより拡張している部分が確認できるだろう。

このように、定義済みのオブジェクトに対して、動的に、汎用的な機能を割り当てるというスタイルはJavaScriptでよく見られるプログラミングスタイルだ。次に挙げるEnumerableオブジェクトも、このように利用されるオブジェクトのひとつである。

Enumerableオブジェクト

少し先に進んで、441行目からEnumerableオブジェクトの定義を見てみよう。

441 var Enumerable = {
442   each: function(iterator) {
443     var index = 0;
444     try {
445       this._each(function(value) {
446         iterator(value, index++);
447       });
448     } catch (e) {
449       if (e != $break) throw e;
450     }
451     return this;
452   },

"Enumerable"を直訳すれば、"可算の/数えられる"という意味の形容詞だ。prototype.jsでは、リスト型のデータ構造として、Enumerableオブジェクトが定義されている。Enumerableオブジェクトが持つプロパティを使うことで、「リストの各要素に繰り返し操作を適用する」などといった処理を、よりシンプルに記述することができる。

さらに先の663行目に目を向けてみよう。

663 Object.extend(Array.prototype, Enumerable);

先ほど(Objectオブジェクトの拡張)と同様の形式で、Enumerableオブジェクトにより、Arrayオブジェクトの拡張がなされている。つまり、prototype.jsを使うときには、通常の配列データに対し、Enumerableオブジェクトが持つプロパティが利用できるということになる。ユーザ定義の(可算集合を扱うような)オブジェクトに、Enumerableオブジェクトを継承させる、というような操作も、同様の形式で可能だ。

さて、ここで登場しているeachプロパティは、引数として渡されたiteratorを配列内の各要素に適用するというメソッドだ。446行目にあるように、iteratorは第一引数に配列の要素、第二引数にインデックスを伴ってコールされる。次のコマンドで動きを確認してみよう。

>>> var bell = new Dog('ベル')
>>> var momo = new Dog('モモ')
>>> var sala = new Dog('サラ')
>>> var arr = [bell, momo, sala]
>>> var iterator = function(e, i) { alert(i + ": " + e.name); }
>>> arr.each(iterator)

Enumerableオブジェクトには、他にも配列を扱う便利なプロパティが数多く定義されている。Enumerableの紹介は残念ながらここまでだが、ぜひ他のプロパティも確認してみてほしい。