ソースの意図の探り方
第三回目です。
オープンソースの成果物を利用する際に,より理解を深めようとするとソースを追っていく必要がでてきます。その際,ソースの特定の箇所が,なぜそうなっているのかが理解できない部分に遭遇することもよくあります。
幸いなことに,多くのオープンソースの配布物はCVSやSubversionなどのバージョン管理システムで管理されており,その履歴が公開されています。
Prototypeライブラリは現在ではRuby on RailsのSubversionリポジトリ上で管理されており,この場合はRails Tracのリポジトリブラウザから辿っていくのがわかりやすくてお勧めです。
ライブラリを利用する際には単一のprototype.jsというファイルとなっていますが,リポジトリ上ではいくつかのファイルに分類されて管理されており,rakeコマンドで結合してリリースされています。
そこで,まずはバージョンの履歴をさかのぼり,コミットログを確認していく,という所からはじめます。Railsのリポジトリはログも丁寧に書かれており,関連するTrack Ticketの番号も明記されています。
では,今回はArray関連からです。
$A()関数
0640: var $A = Array.from = function(iterable) {
0641: if (!iterable) return [];
0642: if (iterable.toArray) {
0643: return iterable.toArray();
0644: } else {
0645: var results = [];
0646: for (var i = 0, length = iterable.length; i < length; i++)
0647: results.push(iterable[i]);
0648: return results;
0649: }
0650: }
0651:
640行目からは$A()関数です。同時にArray.form()というエイリアスも作られています。引数として渡されたオブジェクトを,できるだけ配列型にして返そうとします。
641行目では,渡されたものがそもそも偽として判断されるようなものには空配列を返しています。
次に,渡されたiterableオブジェクトがtoArrayプロパティを持っている場合には,それをメソッドとみなして呼び出し,返り値を返します。EnumerableをmixinしているクラスのオブジェクトはここでtoArray()が使われます。
それ以外の場合,645行目からfor文で中身をひとつずつ取り出してresultsにpush()する,という形で取り出していきます。通常の配列はここの処理となりますが,配列に数字プロパティ以外のプロパティ値がセットされていたとしても,ここではコピーされません。
0652: if (Prototype.Browser.WebKit) {
0653: $A = Array.from = function(iterable) {
0654: if (!iterable) return [];
0655: if (!(typeof iterable == 'function' && iterable == '[object NodeList]') &&
0656: iterable.toArray) {
0657: return iterable.toArray();
0658: } else {
0659: var results = [];
0660: for (var i = 0, length = iterable.length; i < length; i++)
0661: results.push(iterable[i]);
0662: return results;
0663: }
0664: }
0665: }
0666:
652行目からは,$A()関数のApple WebKit対策です。
中身はほぼ同じですが,toArray()を使う条件が異なります。Safariではテキストノードを含むノードリストに対して素の$A()を使うとクラッシュする,という報告があり,条件を満たすオブジェクトの場合はtoArray()を使わずにforループでまわす,としています。
Array オブジェクトへの拡張
0667: Object.extend(Array.prototype, Enumerable);
0668:
Arrayの拡張をするのに,まずEnumerableで定義されているメソッドをまるごとArray.prototypeにmixinします。
0669: if (!Array.prototype._reverse)
0670: Array.prototype._reverse = Array.prototype.reverse;
0671:
後でArray.prototype.reverse()メソッドを上書きしてしまうので,ここでオリジナルの関数オブジェクトを_reverseに保存しておきます。
0672: Object.extend(Array.prototype, {
0673: _each: function(iterator) {
0674: for (var i = 0, length = this.length; i < length; i++)
0675: iterator(this[i]);
0676: },
0677:
mixinしたEnumerable.each()から参照される_each()メソッドです。ここでは単純な配列なので,for文を使って0から順に辿ってiterator関数を呼び出す実装となっています。
0678: clear: function() {
0679: this.length = 0;
0680: return this;
0681: },
0682:
678行目からはclear()メソッドです。配列の場合はlengthプロパティに0をセットするだけで要素をクリアできるので,そのようにしています。
0683: first: function() {
0684: return this[0];
0685: },
0686:
0687: last: function() {
0688: return this[this.length - 1];
0689: },
0690:
683行目からはfirst()メソッドです。配列では単にthis[0]と先頭の要素を返しています。
687行目からのlast()メソッドも簡単で,this[this.length - 1]が最後の要素なのでそれを返します。
0691: compact: function() {
0692: return this.select(function(value) {
0693: return value != null;
0694: });
0695: },
0696:
691行目からはcompact()メソッドです。nullかundefinedな要素を取り除いた配列を返します。
undefinedを!=でnullと比較すると真になるため(undefinedがnullに暗黙的に型変換される),select()に渡す関数内の判別式はvalue != nullという形となります。
0697: flatten: function() {
0698: return this.inject([], function(array, value) {
0699: return array.concat(value && value.constructor == Array ?
0700: value.flatten() : [value]);
0701: });
0702: },
0703:
697行目からはflatten()メソッドです。多層構造の配列をフラットな一次元配列に変換します。階層構造に対応するために,再帰的にflatten()を呼び出すようになっています。
689行目で,空配列を初期値としてinject()を呼び出します。呼び出される関数オブジェクトの中では,Arrayの要素値が真で,かつconstructorプロパティがArrayなら再帰的にflatten()を呼び出してその返り値を,そうでなければその値を配列にしてarray.concat()に渡しています。
new式で生成されたオブジェクトは,prototype.constructorとして生成に用いられた関数オブジェクトを持ちます。ここでは,valueとして渡されたオブジェクトのconstructorプロパティが,Arrayのコンストラクタ関数と等価であるかどうかを見ています。

