prototype.jsを読み解く

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

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

0574:   partition: function(iterator) {
0575:     var trues = [], falses = [];
0576:     this.each(function(value, index) {
0577:       ((iterator || Prototype.K)(value, index) ?
0578:         trues : falses).push(value);
0579:     });
0580:     return [trues, falses];
0581:   },
0582: 

574行目からはpartition()メソッドです。

引数で渡されるinterator関数が真と判断するもの,偽と判断するものに分割し,2要素の配列として返します。

まず575行目で空のtrues,falses配列を用意します。その後,each()でループし,各要素に対して iterator関数を呼び出します。返り値が真か偽かによってtruesかfalsesの配列が返るような三項演算子を使い,その評価値に対してpush()して値を詰め込んでいます。

最後に[ trues, falses ]という配列を返して終了です。

0583:   pluck: function(property) {
0584:     var results = [];
0585:     this.each(function(value, index) {
0586:       results.push(value[property]);
0587:     });
0588:     return results;
0589:   },
0590: 

583行目からはpluck()メソッドです。

各要素の中から指定されたプロパティの値をまとめて返す関数です。

584行目でresultsとして空の配列を用意し,each()を使ってループします。ループ内では単に指定されたプロパティ値を取り出し,resultsにpush()しています。最後にresultsを返します。

0591:   reject: function(iterator) {
0592:     var results = [];
0593:     this.each(function(value, index) {
0594:       if (!iterator(value, index))
0595:         results.push(value);
0596:     });
0597:     return results;
0598:   },
0599: 

591行目からはreject()メソッドです。

findAll()メソッドとほぼ同一で,iterator関数の条件の真偽のみが異なります。結果として,iteratorが偽を返す要素のみをresultsに集めて返しています。

0600:   sortBy: function(iterator) {
0601:     return this.map(function(value, index) {
0602:       return {value: value, criteria: iterator(value, index)};
0603:     }).sort(function(left, right) {
0604:       var a = left.criteria, b = right.criteria;
0605:       return a < b ? -1 : a > b ? 1 : 0;
0606:     }).pluck('value');
0607:   },
0608: 

600行目からはsortBy()メソッドです。指定されたiterator関数が返す値に基づいてソートした配列を返します。

601行目にいきなりreturnが来ていますが,ここではthisに対してmap(),sort(),pluck()を連続して呼び出して,その結果を返しています。

601行目のmap()では,まず要素を{ value:要素値, criteria:iterator関数の返り値 }というオブジェクトに変換し,そのまま配列として返します。

603行目では,標準のArray.sort()関数に渡して,605行目の三項演算子でsort()関数が要求する-1, 0, 1を返しています。

606行目では,そのソート関数が返すソート済みのオブジェクトを,pluck()を使って,予め値が格納してあるvalueプロパティの値を取り出してその配列として返します。

このmap -> sort -> map(今回はpluck())というステップは,Perlの世界ではシュワルツ変換,と呼ばれているものです。

0609:   toArray: function() {
0610:     return this.map();
0611:   },
0612: 

609行目のtoArray()メソッドは,単にmap()メソッドを呼んで返しているだけです。この関数は多くの場合Enumerableを継承したクラスにより上書き定義されます。

0613:   zip: function() {
0614:     var iterator = Prototype.K, args = $A(arguments);
0615:     if (typeof args.last() == 'function')
0616:       iterator = args.pop();
0617: 
0618:     var collections = [this].concat(args).map($A);
0619:     return this.map(function(value, index) {
0620:       return iterator(collections.pluck(index));
0621:     });
0622:   },
0623: 

613行目からはzip()メソッドです。

この関数は,任意の数を引数として受け付けつつ,最後のiterator関数オブジェクトがオプショナルな引数となっています。

そのためまず614行目でiteratorにデフォルト値としてPrototype.Kを代入しておき,argsにargumentsをArray化したものを代入しておきます。argsの最後の要素のtypeofが'function'ならばiteratorが指定されているとみなして,iterator変数にpop()します。

618行目で,zipされる配列の配列を作ります。たとえば[1,2,3].zip([4,5,6],[7,8,9])と呼び出された場合は,collectionsには[[1,2,3],[4,5,6],[7,8,9]]という値が入ります。

ここで,元のEnumerableインスタンスに対してmap()を適用し,各々に対して620行目の処理を行います。ここで,indexは0から呼ばれるごとに増えていくので,最初は0, 次は1, その次は2 となります。collections.pluck(0)の呼び出しは,collectionsの要素のプロパティ"0"の値を返すので,上記の例では[1,4,7]という配列が返り,それがiterator()に渡されます。

これがthis.length分繰り返され,最終的にzipされた配列が返されます(上記例では[[1,4,7],[2,5,8],[3,6,9]]という値⁠⁠。

0624:   size: function() {
0625:     return this.toArray().length;
0626:   },
0627: 

624行目からはsize()メソッドです。Enumerableインスタンスの長さを返します。

toArray()を呼び出してlengthプロパティを取る,という形を取っているのは,Enumerableの実体がArrayとは限らないので,いったんArrayにしているからでしょう。Enumerable.toArray()はeach()を呼び出すのでO(n)の性能になってしまいます。

実際にはEnumerableがmixinされるクラスの側で効率の良い関数として上書きされることが多いようです。

0628:   inspect: function() {
0629:     return '#<Enumerable:' + this.toArray().inspect() + '>';
0630:   }
0631: }
0632: 

628行目からはinspect()メソッドです。ここではEnumerableのinspect()が呼び出されている,ということがわかるようにテキスト表記されたものが返されます。

0633: Object.extend(Enumerable, {
0634:   map:     Enumerable.collect,
0635:   find:    Enumerable.detect,
0636:   select:  Enumerable.findAll,
0637:   member:  Enumerable.include,
0638:   entries: Enumerable.toArray
0639: });

633行目からはEnumerableが提供する関数のエイリアスが5つ定義されています。これらはどっちがメインでどっちがおまけ,という区別はないようで,Prototypeライブラリの中でもどちらが使われているかは一定していません。

著者プロフィール

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

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