prototype.jsを読み解く

第2回 Prototypeライブラリ(198~639行目)

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

0483:   collect: function(iterator) {
0484:     var results = [];
0485:     this.each(function(value, index) {
0486:       results.push((iterator || Prototype.K)(value, index));
0487:     });
0488:     return results;
0489:   },
0490: 

483行目からはcollect()メソッドです。each()を使って渡された関数を呼び出し,(iterator || Prototype.K)(value, index)の呼び出しの返り値をresults配列に集めてそれを返します。

0491:   detect: function(iterator) {
0492:     var result;
0493:     this.each(function(value, index) {
0494:       if (iterator(value, index)) {
0495:         result = value;
0496:         throw $break;
0497:       }
0498:     });
0499:     return result;
0500:   },
0501: 

491行目からはdetect()メソッドです。each()で各要素をチェックし,iterator()関数オブジェクトを実行します。この返り値が真の場合にはresultに要素値を代入した上でthrow $breakでループを抜けます。

もし,interatorが真を返すものが無かった場合,resultの初期値が返ります。492行目でvar resultとなっているので初期値はundefinedとなっています。

0502:   findAll: function(iterator) {
0503:     var results = [];
0504:     this.each(function(value, index) {
0505:       if (iterator(value, index))
0506:         results.push(value);
0507:     });
0508:     return results;
0509:   },
0510: 

502行目からはfindAll()メソッドです。grepのより柔軟なバージョンです。

最初に空の配列resultsを用意して,each()を使ってiteratorで渡された関数が真を返すとresultsにその値をpush()して,最後にそのresultsを返します。

0511:   grep: function(pattern, iterator) {
0512:     var results = [];
0513:     this.each(function(value, index) {
0514:       var stringValue = value.toString();
0515:       if (stringValue.match(pattern))
0516:         results.push((iterator || Prototype.K)(value, index));
0517:     })
0518:     return results;
0519:   },
0520: 

511行目からはgrep()メソッドです。findAll()が一致判定用の関数オブジェクトを渡して判断するのに対して,grep()では比較対象を文字列に変換した上で比較します。

引数patternはString.match()に渡されるので,正規表現オブジェクトを使うことができます。引数iteratorに何も指定しないとPrototype.Kが使われるので,Enumerableインスタンスの各要素値がpush()されますが,iteratorに関数オブジェクトを渡すとその返り値がpush()されるようになっています。

0521:   include: function(object) {
0522:     var found = false;
0523:     this.each(function(value) {
0524:       if (value == object) {
0525:         found = true;
0526:         throw $break;
0527:       }
0528:     });
0529:     return found;
0530:   },
0531: 

521行目からはinclude()メソッドです。

引数として渡されたobjectが,Enumerableの中に含まれているかどうかをチェックしてbooleanで返します。

522行目でfoundの初期値としてfalseを入れておき,each()のループで一致するものが見付かるとfoundにtrueを入れてthrow $breakでループを抜けます。

この時,include()では==演算子を使っていますので,必要に応じて暗黙の型変換が入る場合があります。

0532:   inGroupsOf: function(number, fillWith) {
0533:     fillWith = fillWith === undefined ? null : fillWith;
0534:     return this.eachSlice(number, function(slice) {
0535:       while(slice.length < number) slice.push(fillWith);
0536:       return slice;
0537:     });
0538:   },
0539: 

532行目からはinGroupsOf()メソッドです。eachSlice()メソッドに似ていて,こちらは等サイズに分割した後の余った部分に値を埋めるようになっているので,配列の最後の要素もふくめて全て等しいサイズになります。

533行目でまずfillWithを正規化します。inGroupsOf()の呼び出し時にfillWith引数が指定されなかった場合,fillWith === undefinedが真になります。その場合はnullを代入しておき,そうでなければ元のfillWithをそのまま使います。

あとはeachSlice()を呼び出し,その際にiteratorとして関数を渡し,その中で指定されたサイズに満たない場合にはfillWithで指定された値を補充しています。

0540:   inject: function(memo, iterator) {
0541:     this.each(function(value, index) {
0542:       memo = iterator(memo, value, index);
0543:     });
0544:     return memo;
0545:   },
0546: 

540行目からはinject()メソッドです。Enumerable内の要素に対して順々に何らかの処理を行っていく関数です。

引数memoに渡されるのが初期値で,iterator関数を542行目のような引数を伴って呼び出します。この関数が返す値がmemoとして引き続き使われるようになっています。

このinject()は,ライブラリ内で頻繁に用いられており,ある状態変数を保持した上で各要素に対して何らかの処理を行う,という際に活躍します。

0547:   invoke: function(method) {
0548:     var args = $A(arguments).slice(1);
0549:     return this.map(function(value) {
0550:       return value[method].apply(value, args);
0551:     });
0552:   },
0553: 

547行目からはinvoke()メソッドです。

Enumerableインスタンス内の各要素値に対して,指定されたメソッドを呼び出します。

まず548行目で,invoke()メソッドに渡された二番目以降の引数を取り出して配列としてargsに格納します。これにより可変長の引数を渡すことができます。

後は,map()を使ってインスタンス内の各要素値を引数に550行目の関数を呼び出します。呼び出したい関数名が文字列としてmethodという変数に入っているので,通常のvalue.methodのような形の呼び出しはできませんが,メソッドもプロパティ値として格納されていることには変わりないのでvalue[method]とすることで該当する関数オブジェクトを参照することができます。これに対してapply()を呼び出すことで,該当する関数の呼び出しとなります。この際第一引数のvalueがその関数内でのthisとなります。

0554:   max: function(iterator) {
0555:     var result;
0556:     this.each(function(value, index) {
0557:       value = (iterator || Prototype.K)(value, index);
0558:       if (result == undefined || value >= result)
0559:         result = value;
0560:     });
0561:     return result;
0562:   },
0563: 
0564:   min: function(iterator) {
0565:     var result;
0566:     this.each(function(value, index) {
0567:       value = (iterator || Prototype.K)(value, index);
0568:       if (result == undefined || value < result)
0569:         result = value;
0570:     });
0571:     return result;
0572:   },
0573: 

554行目からはmax()メソッドです。

まず555行目でvar resultを定義します。この段階で中身はundefinedとなります。

その状態でeach()でループを開始し,iteratorが指定されていればその関数が返す値を,そうでなければEnumerable内の値をそのまま比較対照とします。

result にまだ何も入っていない(undefined⁠⁠,もしくはresultよりvalueが大きければresultを更新します(ここでのundefinedとの比較は===を使ったほうが安全な気がします⁠⁠。

最後に残ったresultをmax()の返り値として返します。このため,要素のないEnumerableに対してmax()を呼び出すと,undefinedが返されます。

564行目からはmin()メソッドです。数値比較の部分が>=から<になっている以外はmax()とまったく同一です。

著者プロフィール

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

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