prototype.jsを読み解く

第1回 Prototypeライブラリ(1~197行目)

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

Functionオブジェクトへの拡張

0102: Function.prototype.bind = function() {
0103:   var __method = this, args = $A(arguments), object = args.shift();
0104:   return function() {
0105:     return __method.apply(object, args.concat($A(arguments)));
0106:   }
0107: }
0108: 

Functionオブジェクトのbind()メソッドです。これはFunction.prototype.bindへの代入となっているので,全ての関数に対してbind()メソッドが使えるようになります。

通常のオブジェクトに対するメソッド呼び出しでは,thisがそのオブジェクト自体への参照となりますが,bind()を使うことでその関数内でのthisを指定することができるようになります。

ここでは,関数が呼び出されるタイミングで,bind()呼出し時に渡されたオブジェクトを参照するために,クロージャが使われています。

まず,103行目で__methodに代入されているのは,bind()を呼び出された元の関数オブジェクトです。argsにはbind()呼出し時の引数全体が配列として入ります。objectにはその先頭のオブジェクトがshift()で取り出されて入ります。これが後でthisとして扱われるオブジェクトとなります。

104行目で関数が定義され,bind()の返り値として関数オブジェクトが返るようになっています。ここで,元のbind()関数内のローカル変数である__method, args, objectを105行目のfunction(){ ... }で参照することにより,bind()で返される関数オブジェクトの実行コンテキストにこれらのローカル変数が束縛されます。

bind()の呼び出し自体が終わると,ローカル変数である__methodなどに直接アクセスすることはできなくなりますが,returnで返された関数オブジェクトの中からは引き続き__method, args, objectを参照することができるようになっています。

105行目で使われているargumentsは,返された関数が呼び出される時に渡された引数です。なので,最終的に大元の関数が呼び出される際には,

  1. thisはbind()で指定された最初の引数
  2. arguments(引数の配列)は,bind()で指定された2番目以降の引数に,返された関数の呼出し時に渡された引数を加えたもの

となります。

0109: Function.prototype.bindAsEventListener = function(object) {
0110:   var __method = this, args = $A(arguments), object = args.shift();
0111:   return function(event) {
0112:     return __method.apply(object, [event || window.event].concat(args));
0113:   }
0114: }
0115: 

やっていることはFunction.bind()とほぼ同様です。

違いは,最終的に関数が呼び出される時の引数が,Eventオブジェクトとなる点です。

関数が呼び出される際には,arguments(引数の配列)の最初がEventオブジェクトで,それ以降はbindAsEventListener()を呼び出した際の2番目以降が渡されます。

Number オブジェクトへの拡張

0116: Object.extend(Number.prototype, {
0117:   toColorPart: function() {
0118:     return this.toPaddedString(2, 16);
0119:   },
0120: 
0121:   succ: function() {
0122:     return this + 1;
0123:   },
0124: 
0125:   times: function(iterator) {
0126:     $R(0, this, true).each(iterator);
0127:     return this;
0128:   },
0129: 
0130:   toPaddedString: function(length, radix) {
0131:     var string = this.toString(radix || 10);
0132:     return '0'.times(length - string.length) + string;
0133:   },
0134: 
0135:   toJSON: function() {
0136:     return isFinite(this) ? this.toString() : 'null';
0137:   }
0138: });
0139: 

Object.extend()を使って,JavaScript native objectであるNumberを拡張しています。これもNumber.prototypeを書換えているので,Number全体に作用します。

以下,Numberオブジェクトに関する説明中では,Numberオブジェクト自体が指し示す数値をnと表記します。

117行目からはNumber.toColorPart()メソッドを定義しています。これは後述するtoPaddedStringを使って,nを16進数で2桁の数字の文字列として返します。

CSSなどで,#aabbcc形式の色指定文字列を作成するときに使います。

121行目からは succ()メソッドです。これはsuccessorの略のようで,nの整数における次の数値( すなわち+1したもの)を返します。それほど便利な関数ではないように思えますが,ObjectRangeで使うためにあるようです。

同名のメソッドがStringにも追加されています。

125行目からは times()メソッドです。これは$R()関数を使い,0からn-1までの回数(要するにn回)iteratorを呼び出します。呼び出されるiteratorは関数オブジェクトで,その関数には0からn-1までの数が引数として渡されます。

130行目からはtoPaddingSring()メソッドです。数値を文字列に変換しますが,基数を変換してくれることとと,指定した長さに満たない場合は'0'を足してくれる,という機能があります。

まず131行目でnをradixを基とする数値表現文字列に変換しています。(radix || 10)としているので,radixが0または指定されていない場合には10を指定したのと同じことになります。

その文字列に対して,'0'.times(length - string.length)という形で,318行目で定義されているString.times()を使って必要なだけ'0'という文字を繰り返した文字列を作ります。

最終的にこの'0'...に131行目で作ったstringを接続して返します。

135行目からはtoJSON()メソッドです。まずisFinite()を呼び出し,NaNかどうかを確認しています。もしNaNならJSONとして扱いやすいように'null'という文字列を返し,そうでなければ数値をtoString()した結果を返します。

JavaScriptに存在するNaNをわざわざ'null'に変換してJSON(JavaScript Object Notation)とする,というのも変な話のようにも見えますが,RFC4627などでの定義のなかで,NaNやInfinityは使うことができない,となっているので,toJSON()が返す文字列としては'null'にするしか無いようです。

著者プロフィール

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

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