prototype.jsを読み解く

第3回 Prototypeライブラリ(640~931行目)

この記事を読むのに必要な時間:およそ 10.5 分

Hashオブジェクト

0777: var Hash = function(object) {
0778:   if (object instanceof Hash) this.merge(object);
0779:   else Object.extend(this, object || {});
0780: };
0781: 

777行目からはHashのコンストラクタです。

778行目では,コンストラクタの引数としてHashインスタンスが渡された時に,初期値としてマージします。もしHash以外のオブジェクトが渡された場合は,Object.extend()を使ってプロパティごとにコピーをして初期設定を行います。

0782: Object.extend(Hash, {
0783:   toQueryString: function(obj) {
0784:     var parts = [];
0785:     parts.add = arguments.callee.addPair;
0786: 
0787:     this.prototype._each.call(obj, function(pair) {
0788:       if (!pair.key) return;
0789:       var value = pair.value;
0790: 
0791:       if (value && typeof value == 'object') {
0792:         if (value.constructor == Array) value.each(function(value) {
0793:           parts.add(pair.key, value);
0794:         });
0795:         return;
0796:       }
0797:       parts.add(pair.key, value);
0798:     });
0799: 
0800:     return parts.join('&');
0801:   },
0802: 

783行目からはtoQueryString()メソッドです。

ここではまだHash.toQueryStringとして定義しており,Hash.prototype.toQueryStringはもう少し後ろで別途定義しています。

まず784行目で空の配列を用意し,そのaddプロパティとしてHash.toQueryString.addPair関数オブジェクトを代入しています(基本的には二つの引数を'='でjoin()する関数です⁠⁠。これで,parts.add()が呼び出されたときに,その関数内でのthisはpartsを示すことになります。

787行目では,後ろで定義されるHash.prototype._each()を,objがthisになるような形で呼び出しています。_each()内では{ key:'...', value:'...' }となるオブジェクトが生成され,引数としてイテレータ関数function(pair)に渡されます。

788行目からのイテレータ関数はtoQueryString()に渡したオブジェクトobjのプロパティごとに呼び出されます。オブジェクトのプロパティ値がオブジェクトではなく単なる値の場合,797行目でpartsにadd()されます。

objのプロパティ値が配列だった場合には,その中身が同じプロパティ名で連続してparts.add()されます。たとえばobjが{ a:[1,2,3] }の場合にはtoQueryString()はa=1&a=2&a=3を返します。

_each()のループが終わると,最後にpartsをjoin('&')して繋げて返します。

0803:   toJSON: function(object) {
0804:     var results = [];
0805:     this.prototype._each.call(object, function(pair) {
0806:       var value = Object.toJSON(pair.value);
0807:       if (value !== undefined) results.push(pair.key.toJSON() + ': ' + value);
0808:     });
0809:     return '{' + results.join(', ') + '}';
0810:   }
0811: });
0812: 

803行目からはHash.toJSON()です。これもHash.prototype.toJSON()はまた別途後ろで定義されます。

toQueryString()と同じように,Hash.prototype._each()を呼び出して,各々のプロパティについて処理しています。

value がundefinedの場合は処理しないようにし,そうでなければプロパティ名,プロパティ値共にtoJSON()を呼び出して,': 'で結合しています。ここで,プロパティ名は単なる文字列ですが,値の方は複雑なオブジェクトになりうるので,多くの場合に対応できるObject.toJSON()を使っています。

後は809行目で,'{', '}'で括った文字列として返して終了です。

0813: Hash.toQueryString.addPair = function(key, value, prefix) {
0814:   key = encodeURIComponent(key);
0815:   if (value === undefined) this.push(key);
0816:   else this.push(key + '=' + (value == null ? '' : encodeURIComponent(value)));
0817: }
0818: 

先ほど使ったHash.toQueryString.addPair()関数です。

thisは配列であることを前提とし,key, valueをURIエンコードして'='で繋げた文字列として返します。

815行目にあるように,値がundefinedの場合はキー名だけを文字列として返しています。値がnullの場合は'キー名='と値が空で返ります。

0819: Object.extend(Hash.prototype, Enumerable);
0820: Object.extend(Hash.prototype, {

まずHash.prototypeにEnumerableで定義されたメソッドをまるごとmixinします。その後,Hash独自のメソッドの定義に入ります。

0821:   _each: function(iterator) {
0822:     for (var key in this) {
0823:       var value = this[key];
0824:       if (value && value == Hash.prototype[key]) continue;
0825: 
0826:       var pair = [key, value];
0827:       pair.key = key;
0828:       pair.value = value;
0829:       iterator(pair);
0830:     }
0831:   },
0832: 

まずは_each()メソッドです。Enumerable.each()から呼ばれます。

for (var value in object)構文を使って,インスタンス内のDontEnum属性が付いていないプロパティを列挙していきます。

824行目では,プロパティ値がHash.prototype[key]と同一かどうかを確認しています。これは,スクリプト内でHashに追加したメソッド用のプロパティなどは,DontEnum属性が付いていないためにfor (var ... in ...)で列挙されてしまいます。prototype以下に定義してある型のテンプレートとしてのプロパティは,通常列挙されて欲しくない場合が多いので,prototypeから引き継いだものはスキップされるようにしています。

Hashの場合,prototypeプロパティ以下にはライブラリにより追加されたメソッドが多く含まれています。一方,Hashインスタンス直下には,Hashの用途であるデータの保存を目的として,様々な名前を持ったプロパティが追加されていきます。この際,列挙されるのは自分でインスタンスに追加したデータのみ,という条件が好ましいので,このようにprototype以下との比較チェックが入っています。

あとは826行目からHashのeach()のイテレータ関数で使われるpairオブジェクトを構築しています。これは[ key, value ]の配列として作りつつ,"key", "value"というプロパティも持つ,というオブジェクトになっています。

これを伴いイテレータ関数を呼び出して終了です。

著者プロフィール

栗山淳(くりやまじゅん)

S2ファクトリー株式会社株式会社イメージソース所属。
本業はWeb制作会社の裏方。得意分野はFreeBSDやPerlのはずだが,必要に迫られるとHTML/CSSやJavaScriptも書く。