ソースの意図の探り方
第三回目です。
オープンソースの成果物を利用する際に、
幸いなことに、
Prototypeライブラリは現在ではRuby on RailsのSubversionリポジトリ上で管理されており、
ライブラリを利用する際には単一のprototype.
そこで、
では、
$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.
641行目では、
次に、
それ以外の場合、
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行目からは、
中身はほぼ同じですが、
Array オブジェクトへの拡張
0667: Object.extend(Array.prototype, Enumerable);
0668:
Arrayの拡張をするのに、
0669: if (!Array.prototype._reverse)
0670: Array.prototype._reverse = Array.prototype.reverse;
0671:
後でArray.
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.
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()メソッドも簡単で、
0691: compact: function() {
0692: return this.select(function(value) {
0693: return value != null;
0694: });
0695: },
0696:
691行目からはcompact()メソッドです。nullかundefinedな要素を取り除いた配列を返します。
undefinedを!=で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()メソッドです。多層構造の配列をフラットな一次元配列に変換します。階層構造に対応するために、
689行目で、
new式で生成されたオブジェクトは、
0704: without: function() {
0705: var values = $A(arguments);
0706: return this.select(function(value) {
0707: return !values.include(value);
0708: });
0709: },
0710:
704行目からはwithout()メソッドです。まず、
706行目でselect()を使って、
この結果、
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が、
712行目からのシンプルなfor文での実装となっています。もし見つからなければ714行目で-1を返しています。
0717: reverse: function(inline) {
0718: return (inline !== false ? this : this.toArray())._reverse();
0719: },
0720:
717行目からはreverse()メソッドです。これは、
引数を指定しない場合はArrayインスタンスの中身を直接逆順に並べ替えますが、
_reverse()にオリジナルのArray.
0721: reduce: function() {
0722: return this.length > 1 ? this : this[0];
0723: },
0724:
721行目からはreduce()メソッドです。個人的にはあまり用途がわからない関数ですが、
要素数が0の場合もthis[0]として返されるので、
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()を空配列を初期値として呼び出して、
呼び出されるイテレータ関数の中では、
最後のinclude()を使ったチェックが、
0733: clone: function() {
0734: return [].concat(this);
0735: },
0736:
733行目からはclone()メソッドです。配列をコピーする方法はいろいろとあると思いますが、
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.
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を空で用意して、
イテレータ関数の内部では、
0755: Array.prototype.toArray = Array.prototype.clone;
0756:
755行目で、
0757: function $w(string) {
0758: string = string.strip();
0759: return string ? string.split(/\s+/) : [];
0760: }
0761:
757行目からは$w()関数です。
まずString.
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.
どのバージョンのOperaのネイティブの実装に問題があるのか、
Hashオブジェクト
0777: var Hash = function(object) {
0778: if (object instanceof Hash) this.merge(object);
0779: else Object.extend(this, object || {});
0780: };
0781:
777行目からはHashのコンストラクタです。
778行目では、
0782: Object.extend(Hash, {
0783: toQueryString: function(obj) {
0784: var parts = [];
0785: parts.add = arguments.callee.addPair;
0786:
0787: this.prototype._each.call(obj, function(pair) {
0788: if (!pair.key) return;
0789: var value = pair.value;
0790:
0791: if (value && typeof value == 'object') {
0792: if (value.constructor == Array) value.each(function(value) {
0793: parts.add(pair.key, value);
0794: });
0795: return;
0796: }
0797: parts.add(pair.key, value);
0798: });
0799:
0800: return parts.join('&');
0801: },
0802:
783行目からはtoQueryString()メソッドです。
ここではまだHash.
まず784行目で空の配列を用意し、
787行目では、
788行目からのイテレータ関数はtoQueryString()に渡したオブジェクトobjのプロパティごとに呼び出されます。オブジェクトのプロパティ値がオブジェクトではなく単なる値の場合、
objのプロパティ値が配列だった場合には、
_each()のループが終わると、
0803: toJSON: function(object) {
0804: var results = [];
0805: this.prototype._each.call(object, function(pair) {
0806: var value = Object.toJSON(pair.value);
0807: if (value !== undefined) results.push(pair.key.toJSON() + ': ' + value);
0808: });
0809: return '{' + results.join(', ') + '}';
0810: }
0811: });
0812:
803行目からはHash.
toQueryString()と同じように、
value がundefinedの場合は処理しないようにし、
後は809行目で、
0813: Hash.toQueryString.addPair = function(key, value, prefix) {
0814: key = encodeURIComponent(key);
0815: if (value === undefined) this.push(key);
0816: else this.push(key + '=' + (value == null ? '' : encodeURIComponent(value)));
0817: }
0818:
先ほど使ったHash.
thisは配列であることを前提とし、
815行目にあるように、
0819: Object.extend(Hash.prototype, Enumerable);
0820: Object.extend(Hash.prototype, {
まずHash.
0821: _each: function(iterator) {
0822: for (var key in this) {
0823: var value = this[key];
0824: if (value && value == Hash.prototype[key]) continue;
0825:
0826: var pair = [key, value];
0827: pair.key = key;
0828: pair.value = value;
0829: iterator(pair);
0830: }
0831: },
0832:
まずは_each()メソッドです。Enumerable.
for (var value in object)構文を使って、
824行目では、
Hashの場合、
あとは826行目からHashのeach()のイテレータ関数で使われるpairオブジェクトを構築しています。これは[ key, value ]の配列として作りつつ、
これを伴いイテレータ関数を呼び出して終了です。
0833: keys: function() {
0834: return this.pluck('key');
0835: },
0836:
0837: values: function() {
0838: return this.pluck('value');
0839: },
0840:
833行目からはkeys()メソッドです。Enumerable.
837行目からのvalues()メソッドも同様で、
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化し、
あとはイテレータ関数に来たpairオブジェクトを使って、
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()メソッドです。
この関数は取り除いたプロパティの値を返すので、
まず、
853行目では、
あとは指定されたプロパティをdeleteで消し、
0864: toQueryString: function() {
0865: return Hash.toQueryString(this);
0866: },
0867:
864行目の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であることがわかるように文字列化し、
0874: toJSON: function() {
0875: return Hash.toJSON(this);
0876: }
0877: });
0878:
874行目からはtoJSON()メソッドです。これもHash.
0879: function $H(object) {
0880: if (object instanceof Hash) return object;
0881: return new Hash(object);
0882: };
0883:
879行目からは$H()関数です。
渡されたオブジェクトがすでに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行目からは、
まず、
886行目からの処理で判別しているのは、
ここではブラウザのUserAgentを使った判別は行わず、
890行目からの、
ObjectRangeクラス
0902: ObjectRange = Class.create();
0903: Object.extend(ObjectRange.prototype, Enumerable);
902行目からはObjectRangeクラスです。
まずはClass.
0904: Object.extend(ObjectRange.prototype, {
0905: initialize: function(start, end, exclusive) {
0906: this.start = start;
0907: this.end = end;
0908: this.exclusive = exclusive;
0909: },
0910:
コンストラクタでは、
0911: _each: function(iterator) {
0912: var value = this.start;
0913: while (this.include(value)) {
0914: iterator(value);
0915: value = value.succ();
0916: }
0917: },
0918:
Enumerable.
ObjectRangeオブジェクトは、
そのため、
0919: include: function(value) {
0920: if (value < this.start)
0921: return false;
0922: if (this.exclusive)
0923: return value < this.end;
0924: return value <= this.end;
0925: }
0926: });
0927:
919行目からはinclude()メソッドです。
最初はthis.
this.
this.
0928: var $R = function(start, end, exclusive) {
0929: return new ObjectRange(start, end, exclusive);
0930: }
0931:
928行目からは$R()ショートカット関数です。
単にnewでObjectRangeインスタンスを作成し、