prototype.jsを読み解く

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

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

0704:   without: function() {
0705:     var values = $A(arguments);
0706:     return this.select(function(value) {
0707:       return !values.include(value);
0708:     });
0709:   },
0710: 

704行目からはwithout()メソッドです。まず,可変長の引数として渡されているargumentsをvaluesに配列としてまとめます。

706行目でselect()を使って,該当する要素だけを取り出して返します。その際に,関数オブジェクトとして渡している条件判断では,values.include (value)として,Arrayの要素値valueが,values配列の中に含まれているかどうかをチェックしbooleanで返しています(include()では==で比較しています⁠⁠。

この結果,Arrayインスタンスの要素値のうち,valuesに含まれないもののみが配列として返される形になります。

0711:   indexOf: function(object) {
0712:     for (var i = 0, length = this.length; i < length; i++)
0713:       if (this[i] == object) return i;
0714:     return -1;
0715:   },
0716: 

711行目からはindexOf()メソッドです。渡されたobjectが,Array要素内に存在すればそのインデックス値を返します。

712行目からのシンプルなfor文での実装となっています。もし見つからなければ714行目で-1を返しています。

0717:   reverse: function(inline) {
0718:     return (inline !== false ? this : this.toArray())._reverse();
0719:   },
0720: 

717行目からはreverse()メソッドです。これは,元々存在するArray.reverse()を置き換えています。

引数を指定しない場合はArrayインスタンスの中身を直接逆順に並べ替えますが,false を指定すると,インスタンスのコピーを作成し,それを逆順にして返すようになっています。

_reverse()にオリジナルのArray.reverse()関数が保存されているので,処理対象をinline引数を見てthisにするかthis.toArray()でコピーしたものにするか,を変えています。

0721:   reduce: function() {
0722:     return this.length > 1 ? this : this[0];
0723:   },
0724: 

721行目からはreduce()メソッドです。個人的にはあまり用途がわからない関数ですが,lengthプロパティを調べて要素数が1より大きければ配列のまま返し,それ以下なら先頭の要素を(配列を解いて)返しています。

要素数が0の場合もthis[0]として返されるので,その場合はundefinedが返されます。

0725:   uniq: function(sorted) {
0726:     return this.inject([], function(array, value, index) {
0727:       if (0 == index || (sorted ? array.last() != value : !array.include(value)))
0728:         array.push(value);
0729:       return array;
0730:     });
0731:   },
0732: 

725行目からはuniq()メソッドです。inject()を空配列を初期値として呼び出して,その結果を返しています。

呼び出されるイテレータ関数の中では,配列の先頭の場合(index == 0)にはそのままarrayにpush()します。それ以外の場合はuniq()に渡されるsortedフラグに依存して,sortedが真の場合,直前にarrayにpush()したものと異なっていれば今回の値もpush()する,で済みます。sortedが偽の場合,array.include()を呼び出して過去にpush()したarrayの中に今回の値が含まれているかどうかをチェックして,存在しなければpush()する,とします。

最後のinclude()を使ったチェックが,長い配列だと遅くなりうる処理になるので注意しましょう。ソートされた配列を使ってsortedフラグを真にすれば,include()を使わずに済みます。

0733:   clone: function() {
0734:     return [].concat(this);
0735:   },
0736: 

733行目からはclone()メソッドです。配列をコピーする方法はいろいろとあると思いますが,ここでは空配列に対してArray.concat()を使うことでコピーを作成しています。

0737:   size: function() {
0738:     return this.length;
0739:   },
0740: 

737行目からはsize()メソッドです。Arrayなので単純にlengthプロパティを返しています。

0741:   inspect: function() {
0742:     return '[' + this.map(Object.inspect).join(', ') + ']';
0743:   },
0744: 

741行目からはArrayのinspect()メソッドです。

JSON 表記と同じように(というのも変ですね。リテラルな配列オブジェクト表記,でしょうか⁠⁠,'[', ']' で要素を囲んで出力しています。

各要素の出力はObject.inspect()に任せて,各々が適切な文字列表記を返してもらいます。返ってきた配列をjoin(', ')で連結して終了です。

0745:   toJSON: function() {
0746:     var results = [];
0747:     this.each(function(object) {
0748:       var value = Object.toJSON(object);
0749:       if (value !== undefined) results.push(value);
0750:     });
0751:     return '[' + results.join(', ') + ']';
0752:   }
0753: });
0754: 

745行目からはArray用のtoJSON()メソッドです。

格納場所として配列resultsを空で用意して,Array インスタンスに対してeach()ループを実行します。

イテレータ関数の内部では,Object.toJSON()を呼び出し,返り値がundefinedでない時のみresultsにpush()しています。Object.toJSON()からundefinedが返るのは,要素がundefined, 関数の時などです。

0755: Array.prototype.toArray = Array.prototype.clone;
0756: 

755行目で,Array.clone()の別名としてArray.toArray()を用意しています。

0757: function $w(string) {
0758:   string = string.strip();
0759:   return string ? string.split(/\s+/) : [];
0760: }
0761: 

757行目からは$w()関数です。

まずString.strip()で前後の空白(\s)を取り除き,stringが空でなければsplit(/\s+/)したものを,空なら[]で空配列を返します。

0762: if (Prototype.Browser.Opera){
0763:   Array.prototype.concat = function() {
0764:     var array = [];
0765:     for (var i = 0, length = this.length; i < length; i++) array.push(this[i]);
0766:     for (var i = 0, length = arguments.length; i < length; i++) {
0767:       if (arguments[i].constructor == Array) {
0768:         for (var j = 0, arrayLength = arguments[i].length; j < arrayLength; j++)
0769:           array.push(arguments[i][j]);
0770:       } else {
0771:         array.push(arguments[i]);
0772:       }
0773:     }
0774:     return array;
0775:   }
0776: }

762行目からは,Opera用にArray.concat()関数を再定義しています。

Operaがネイティブに実装しているArray.concat()が正しく動かないので,上書きするということで,Revision 5550で追加されています。

どのバージョンのOperaのネイティブの実装に問題があるのか,どういう問題なのか,は未調査です。

著者プロフィール

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

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