prototype.jsを読み解く

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

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

Dateオブジェクトへの拡張

0140: Date.prototype.toJSON = function() {
0141:   return '"' + this.getFullYear() + '-' +
0142:     (this.getMonth() + 1).toPaddedString(2) + '-' +
0143:     this.getDate().toPaddedString(2) + 'T' +
0144:     this.getHours().toPaddedString(2) + ':' +
0145:     this.getMinutes().toPaddedString(2) + ':' +
0146:     this.getSeconds().toPaddedString(2) + '"';
0147: };
0148: 

native objectであるDateを拡張して,toJSON()メソッドを追加しています。

記述が長いのでわかりにくいのですが,"2007-07-03T20:13:15" という表記の文字列を返します(二重引用符も含む⁠⁠。桁数を揃えるためにNumber.toPaddidString()を使っています。

Tryオブジェクト

0149: var Try = {
0150:   these: function() {
0151:     var returnValue;
0152: 
0153:     for (var i = 0, length = arguments.length; i < length; i++) {
0154:       var lambda = arguments[i];
0155:       try {
0156:         returnValue = lambda();
0157:         break;
0158:       } catch (e) {}
0159:     }
0160: 
0161:     return returnValue;
0162:   }
0163: }
0164: 

Tryオブジェクトを定義していますが,現状では使われているのはTry.these()関数だけです。

Try.these()関数は,引数として任意の数の関数オブジェクトを受け取ります。任意の数の引数を受け取れるようにするために,150行目の関数定義では引数を明示せず,153行目のfor文でarguments配列をひとつずつ処理しています。

引数の先頭から処理していき,その関数オブジェクトの呼び出し(156行目)が例外を発生させずに終了できれば,breakでfor文を抜け,その関数の返り値returnValueをTry.these()の呼出し元に返します。

例外が発生した場合,特にその例外に対しては何もせず(158行目のからcatch()節⁠⁠,次の引数の関数オブジェクトを試します。

今のところ,Prototypeライブラリ内ではこの関数はAjaxオブジェクトのXMLHttpRequest,ActiveXObjectを取得する際にのみ使われています。

PeriodicalExecuterクラス

0165: /*--------------------------------------------------------------------------*/
0166: 
0167: var PeriodicalExecuter = Class.create();
0168: PeriodicalExecuter.prototype = {
0169:   initialize: function(callback, frequency) {
0170:     this.callback = callback;
0171:     this.frequency = frequency;
0172:     this.currentlyExecuting = false;
0173: 
0174:     this.registerCallback();
0175:   },
0176: 
0177:   registerCallback: function() {
0178:     this.timer = setInterval(this.onTimerEvent.bind(this), this.frequency * 1000);
0179:   },
0180: 
0181:   stop: function() {
0182:     if (!this.timer) return;
0183:     clearInterval(this.timer);
0184:     this.timer = null;
0185:   },
0186: 
0187:   onTimerEvent: function() {
0188:     if (!this.currentlyExecuting) {
0189:       try {
0190:         this.currentlyExecuting = true;
0191:         this.callback(this);
0192:       } finally {
0193:         this.currentlyExecuting = false;
0194:       }
0195:     }
0196:   }
0197: }

PeriodicalExecuterクラスです。Class.create()を使って雛型を作り,168行目でprototypeプロパティを上書きすることで各メソッドを定義しています。

169行目からがコンストラクタです。メンバー変数としてcallback,frequencyを設定し,currentlyExecutingフラグをfalseで初期化します。最後にregisterCallback()を呼んで,最初のタイマーを登録します。

177行目からがそのregisterCallback()メソッドで,setInterval()を使って周期的なタイマーを設定しています。ここでthis.onTimerEvent.bind(this)は定型句のようなもので,自分のonTimerEventメソッドが,thisが現在と同じ自分自身を指した状態で呼ばれるようにしています。this.frequencyは1000倍しているので,PeriodicalExecuterの生成時に渡すfrequencyの値の単位は秒となります。

先に 187行目のonTimerEvent()メソッドを確認しましょう。ここでは,一番外側でthis.currentlyExecutingフラグを見て,falseだった時のみ内側のコードが実行されるようになっています。

そして,try句の中でいったんthis.currentlyExecutingをtrueにした状態でthis.callback()を呼び出し,それが終わるとfinally節で再度this.currentlyExecutingをfalseに戻します。

こうすることによって,時間がどれくらいかかるのかわからないcallback関数を呼び出している間はcurrentlyExecutingがtrueになるようになり,その実行中に再度setInterval()の割り込みがあった場合でも並列して多重に実行されることが無いようになっています。

逆にいうと,たとえfrequencyを1秒に設定したとしても,60秒で確実に60回呼び出されるわけではなく,callback関数の費す時間によっては少ない回数しか呼び出されない可能性があります。

最後に181行目のstop()メソッドで,タイマーを停止します。178行目でthis.timerを保存していれば,そのタイマーIDを使ってclearInterval()を呼び出してタイマーを停止し,再度同じことをしないようにthis.timerをnullとして後片付けをします。

著者プロフィール

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

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