prototype.jsを読み解く

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

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

0833:   keys: function() {
0834:     return this.pluck('key');
0835:   },
0836: 
0837:   values: function() {
0838:     return this.pluck('value');
0839:   },
0840: 

833行目からはkeys()メソッドです。Enumerable.pluck()を利用し,Hash.prototype._each()でpairオブジェクトの'key'プロパティの値を配列として返すようになっています。

837行目からのvalues()メソッドも同様で,pairオブジェクトの'value'プロパティを参照しているだけの違いです。

0841:   merge: function(hash) {
0842:     return $H(hash).inject(this, function(mergedHash, pair) {
0843:       mergedHash[pair.key] = pair.value;
0844:       return mergedHash;
0845:     });
0846:   },
0847: 

841行目からはmerge()メソッドです。

$H()で引数で渡されたオブジェクトをHash化し,thisを初期値としてinject()を呼び出します。

あとはイテレータ関数に来たpairオブジェクトを使って,mergedHashにプロパティを追加していき,最終的にinject()から返ってきたHashを返します。

0848:   remove: function() {
0849:     var result;
0850:     for(var i = 0, length = arguments.length; i < length; i++) {
0851:       var value = this[arguments[i]];
0852:       if (value !== undefined){
0853:         if (result === undefined) result = value;
0854:         else {
0855:           if (result.constructor != Array) result = [result];
0856:           result.push(value)
0857:         }
0858:       }
0859:       delete this[arguments[i]];
0860:     }
0861:     return result;
0862:   },
0863: 

848行目からはremove()メソッドです。

この関数は取り除いたプロパティの値を返すので,その保存用にresult変数を定義します。remove()の引数は可変長のため,arguments変数をfor文でループして,ひとつずつ処理します。

まず,851行目でHashインスタンスからプロパティ値を取り出そうとします。値がundefinedなら,そのHashには指定されたプロパティが存在しなかった,ということでスキップします。

853行目では,まだresultを使っていない時の処理で,単に値を代入します。もし2回目以降の場合,resultがまだ配列でなければ1要素の配列に変換します。その後,今回の値をpush()します。resultは返す値がひとつしかなければそのまま,複数なら配列として返されるようになっているのでこういう処理となっています。

あとは指定されたプロパティをdeleteで消し,ループが終わるとresultに入った値を返します。

0864:   toQueryString: function() {
0865:     return Hash.toQueryString(this);
0866:   },
0867: 

864行目のtoQueryString()メソッドは,Hash.toQueryString()で定義した関数を呼ぶだけです。

Hash.toQueryString()関数は,インスタンスのメソッドとして以外でも使えるように,グローバルはHashオブジェクト直下に置かれています。インスタンスから呼ばれる時は,このメソッドを経由して呼ばれる形になります。

0868:   inspect: function() {
0869:     return '#<Hash:{' + this.map(function(pair) {
0870:       return pair.map(Object.inspect).join(': ');
0871:     }).join(', ') + '}>';
0872:   },
0873: 

868行目からはHash版のinspect()メソッドです。

'#<Hash:{...}>'という表記でHashであることがわかるように文字列化し,中身は万能Object.inspect()を使って出力します。

0874:   toJSON: function() {
0875:     return Hash.toJSON(this);
0876:   }
0877: });
0878: 

874行目からはtoJSON()メソッドです。これもHash.toJSON()をそのまま呼んでいるだけです。

0879: function $H(object) {
0880:   if (object instanceof Hash) return object;
0881:   return new Hash(object);
0882: };
0883: 

879行目からは$H()関数です。

渡されたオブジェクトがすでにHashインスタンスならそのまま返し,そうでなければnew Hash()を使ってHashインスタンスとします。これで$()関数と同様に,⁠渡すものがHashかどうかわからないけどとにかくHashにしてくれ」という形で使うことができるので便利です。

0884: // Safari iterates over shadowed properties
0885: if (function() {
0886:   var i = 0, Test = function(value) { this.key = value };
0887:   Test.prototype.key = 'foo';
0888:   for (var property in new Test('bar')) i++;
0889:   return i > 1;
0890: }()) Hash.prototype._each = function(iterator) {
0891:   var cache = [];
0892:   for (var key in this) {
0893:     var value = this[key];
0894:     if ((value && value == Hash.prototype[key]) || cache.include(key)) continue;
0895:     cache.push(key);
0896:     var pair = [key, value];
0897:     pair.key = key;
0898:     pair.value = value;
0899:     iterator(pair);
0900:   }
0901: };

884行目からは,Safari用にHash.prototype._each()を入れ替えています。

まず,885行目からのfunction(){ ... }()という文は,名前空間を汚さずにローカル変数を使った処理を書くときの定型句です。リテラルな関数オブジェクトを作って,それを()演算子で呼び出しています。関数内でvarで定義した変数はその外のスコープからは見えません。

886行目からの処理で判別しているのは,SafariがFoo.prototype.barとFoo.barというプロパティが存在するときに,barを二回列挙してしまう,という問題があるためです。

ここではブラウザのUserAgentを使った判別は行わず,実際に試してみて判別しています。

890行目からの,別バージョンのHash.prototype._eachは,821行目からのオリジナルとあまり変わらず,列挙されるプロパティ名をcache変数に貯めておき,同じプロパティ名が再度出てきたらスキップする,という処理を加えただけです。

著者プロフィール

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

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