prototype.jsを読み解く

第4回 Prototypeライブラリ(932~1289行目)

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

リポジトリの差分を追う

第四回目です。

前回,Subversionのログを見るためのTracのリポジトリブラウザを紹介しました。単にログを見る場合,時系列にコミットログを追っていくことはできますが,実際に知りたいのは,このコード片の変更がどのリビジョンで行われたか,でしょう。

これはSubversionのannotateサブコマンドで確認することができます。

% svn annotate http://svn.rubyonrails.org/rails/spinoffs/prototype/trunk/src/prototype.js
  3362        sam <%= include 'HEADER' %>
  3362        sam
  3362        sam var Prototype = {
...

これでその行が最後に変更されたリビジョンがわかるので,そのログや差分を確認します。そのリビジョンが目当てとする変更ではなかった場合,"svn annotate -r 一つ前のリビジョン" を使ってさらにさかのぼっていきます。

最終的に変更点のリビジョンを突き止めたら,そのログやTrac Ticket番号を頼りに内容を理解する,という形になります。

では,今回はAjaxオブジェクトからです。

Ajaxオブジェクト

0932: var Ajax = {
0933:   getTransport: function() {
0934:     return Try.these(
0935:       function() {return new XMLHttpRequest()},
0936:       function() {return new ActiveXObject('Msxml2.XMLHTTP')},
0937:       function() {return new ActiveXObject('Microsoft.XMLHTTP')}
0938:     ) || false;
0939:   },
0940: 
0941:   activeRequestCount: 0
0942: }
0943: 

Ajaxは複数のクラスを包含するための名前空間となっており,まずAjax直下にはヘルパ関数getTransport()と,静的変数activeRequestCountが定義されています。

getTransport()関数は,前述したTry.these()関数を使い,複数のアクションを連続して試みています。935行目からにあるように,最初にXMLHttpRequest()をnewしてみて,成功したらそのインスタンスを返します。残りの古いIE用のActiveXObjectの生成も同様です。Try.these()にあるうちの,いずれも失敗した場合には最終的にfalseが返ります。

941行目のactiveRequestCountは,後述するAjax.Respondersで使われます。

0944: Ajax.Responders = {
0945:   responders: [],
0946: 
0947:   _each: function(iterator) {
0948:     this.responders._each(iterator);
0949:   },
0950: 

Ajax.RespondersはXHR接続に対するイベントを管理するオブジェクトです。

register(),unregister()で利用するために,静的な配列respondersを空で初期化しています。

Ajax.Respondersは後でEnumerableをmixinするため,Enumerable.each()から呼び出される_each()メソッドを実装しています。

948行目では,945行目で定義したresponders配列のArray._each()を呼び出しています。

0951:   register: function(responder) {
0952:     if (!this.include(responder))
0953:       this.responders.push(responder);
0954:   },
0955: 
0956:   unregister: function(responder) {
0957:     this.responders = this.responders.without(responder);
0958:   },
0959: 

951行目からのregister()関数では,引数として渡されたresponderがAjax.Responders.responders配列に含まれているかを確認し,無ければpush()して追加します。

ここで,Ajax.Respondersはnewして使うようなクラスの実装とはなっていません。使う場合はAjax.Responders.register(...)という形で呼び出します。

このとき,register()関数のthisはグローバルなAjax.Respondersオブジェクトを示します。そのため,953行目のthis.respondersは,945行目で定義しているresponders配列を示すことになります。

956行目からはunregister()関数です。Array.without()メソッドを使って,指定されたものが含まれないresponders配列を作り直しています。

0960:   dispatch: function(callback, request, transport, json) {
0961:     this.each(function(responder) {
0962:       if (typeof responder[callback] == 'function') {
0963:         try {
0964:           responder[callback].apply(responder, [request, transport, json]);
0965:         } catch (e) {}
0966:       }
0967:     });
0968:   }
0969: };
0970: 

960行目からのdispatch()関数は,Prototypeライブラリ内部で利用される関数です。登録されたイベント処理関数を要求されたイベントタイプに基づいて呼び出す仕事をします。

最初の引数callbackには,'onCreate', 'onUninitialized', 'onLoading', 'onLoaded', 'onInteractive', 'onSuccess'/'onFailure', 'onComplete'などの文字列が渡されます。request,transport,jsonは,イベント処理関数に渡されるパラメータです。

961行目のeach()で,responders配列に含まれるすべてのオブジェクトに対してループ処理をします。その中で,登録されたオブジェクトが,'onCreate'などのイベント名に対応したプロパティをもっており,かつそれが関数オブジェクトだった場合に,apply()を使って関数を呼び出します。apply()の第一引数がresponderなので,これがイベント処理関数の中ではこれがthisとなります。

0971: Object.extend(Ajax.Responders, Enumerable);
0972: 

Ajax.RespondersにEnumerableをmixinして,Enumerableとして振舞うようにしています。

0973: Ajax.Responders.register({
0974:   onCreate: function() {
0975:     Ajax.activeRequestCount++;
0976:   },
0977:   onComplete: function() {
0978:     Ajax.activeRequestCount--;
0979:   }
0980: });
0981: 

973行目で,Ajax.Responders.register()を呼び出し,最初のresponderを登録しています。

これにより,Ajax.Responders.dispatch('onCreate', ...)が呼び出されると,Ajax.activeRequestCountがインクリメントされ,'onComlete'の際にはデクリメントされることになります。dispatch()はPrototypeライブラリ内のXHR呼び出しを管理する部分で自動的に呼び出してくれるので,このregister()だけでこのような処理が可能となります。

0982: Ajax.Base = function() {};
0983: Ajax.Base.prototype = {
0984:   setOptions: function(options) {
0985:     this.options = {
0986:       method:       'post',
0987:       asynchronous: true,
0988:       contentType:  'application/x-www-form-urlencoded',
0989:       encoding:     'UTF-8',
0990:       parameters:   ''
0991:     }
0992:     Object.extend(this.options, options || {});
0993: 
0994:     this.options.method = this.options.method.toLowerCase();
0995:     if (typeof this.options.parameters == 'string')
0996:       this.options.parameters = this.options.parameters.toQueryParams();
0997:   }
0998: }
0999: 

他のAjax.*クラスで利用するための,Ajax.Baseというオブジェクトを定義しています。

982行目でコンストラクタ用に空の関数を定義して,後はprototypeプロパティ以下にsetOptions()というメソッドを定義しているだけです。

ここでは,インスタンスのthis.optionsというプロパティにデフォルトの値のセットを定義し,引数経由で渡されたオブジェクトで上書きしています。Object.extend()の第一引数にデフォルトとなる値を集めたオブジェクトを渡し,第二引数で関数利用者が必要なものだけを指定して上書きする,という形は常用される定型句です。

994行目ではoptions.methodを小文字に正規化しています。

options.parametersは,文字列でも,Hash互換のオブジェクトでも受け付けるようになっているので,もし文字列だった場合にはtoQueryParams()で一旦Hashオブジェクトの形式に変換しておきます。

著者プロフィール

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

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

コメント

コメントの記入