prototype.jsを読み解く

第10回 Prototypeライブラリ(2846~3276行目)

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

3012:   unloadCache: function() {
3013:     if (!Event.observers) return;
3014:     for (var i = 0, length = Event.observers.length; i < length; i++) {
3015:       Event.stopObserving.apply(this, Event.observers[i]);
3016:       Event.observers[i][0] = null;
3017:     }
3018:     Event.observers = false;
3019:   },
3020: 

Event.unloadCache()関数です。3052行目でwindowにonUnloadイベントのハンドラとして,この関数が登録されています。

IEで動的にイベントを追加すると,ページ遷移してもその分のメモリが開放されないようなので,それを回避するために onUnload でこの関数が呼ばれ,Event.observers に蓄積された登録済みイベントを,すべて Event.stopObserving() を使ってイベント登録を解除しています。

3016行目では,登録されたイベントハンドラEvent.observers[i]のプロパティ'0'に対してnullを代入しています。これが何を意図しているかはわかりませんでした。

3021:   observe: function(element, name, observer, useCapture) {
3022:     element = $(element);
3023:     useCapture = useCapture || false;
3024: 
3025:     if (name == 'keypress' &&
3026:       (Prototype.Browser.WebKit || element.attachEvent))
3027:       name = 'keydown';
3028: 
3029:     Event._observeAndCache(element, name, observer, useCapture);
3030:   },
3031: 

3021行目からはEvent.observe()です。

引数useCaptureが省略された場合はundefinedになるので,その場合はfalseにしておきます。

あとは受け取った引数をEvent._observeAndCache()に渡してイベント登録して終了です。

このobserve()と次のstopObserving()でのkeypress, keydownイベントの扱いは,Operaで問題となります。

下記リンクに記載されている修正でいいかと思いますが,Prototype 1.6.0では該当部分がごっそり書き換わってしまったので,適用できるのは1.5.1.1までとなります。

3032:   stopObserving: function(element, name, observer, useCapture) {
3033:     element = $(element);
3034:     useCapture = useCapture || false;
3035: 
3036:     if (name == 'keypress' &&
3037:         (Prototype.Browser.WebKit || element.attachEvent))
3038:       name = 'keydown';
3039: 
3040:     if (element.removeEventListener) {
3041:       element.removeEventListener(name, observer, useCapture);
3042:     } else if (element.detachEvent) {
3043:       try {
3044:         element.detachEvent('on' + name, observer);
3045:       } catch (e) {}
3046:     }
3047:   }
3048: });
3049: 

3032行目からはstopObserving()です。

observe()と同様に引数のuseCapture, nameを整理して,DOM準拠ブラウザ,IEで分岐してそれぞれremoveEventListener()かdetachEvent()を使ってイベント登録を解除しています。

detachEvent()がtry {}で括られているのは,IEでの例外対策とのことです。

3050: /* prevent memory leaks in IE */
3051: if (Prototype.Browser.IE)
3052:   Event.observe(window, 'unload', Event.unloadCache, false);

ブラウザがIEの時のみ,windowのonUnloadイベント時にEvent.unloadCache()関数が呼ばれるようにしてメモリリークを防いでいます。

Position オブジェクト

3053: var Position = {
3054:   // set to true if needed, warning: firefox performance problems
3055:   // NOT neeeded for page scrolling, only if draggable contained in
3056:   // scrollable elements
3057:   includeScrollOffsets: false,
3058: 

最後のオブジェクトPositionです。特にクラス化はしておらず,Position.prepare()などの形で呼び出されることになります。

また,Prototype 1.6.0以降では,Elementに機能が統合されたため,Positionオブジェクト自体が非推奨となっています。

3057行目のincludeScrollOffsetsはbooleanで,今のところはfalseで固定されているようです。Position.within()の対象がスクロールするものの時にだけtrueにしろ,とコメントでは言及されています。

3059:   // must be called before calling withinIncludingScrolloffset, every time the
3060:   // page is scrolled
3061:   prepare: function() {
3062:     this.deltaX =  window.pageXOffset
3063:                 || document.documentElement.scrollLeft
3064:                 || document.body.scrollLeft
3065:                 || 0;
3066:     this.deltaY =  window.pageYOffset
3067:                 || document.documentElement.scrollTop
3068:                 || document.body.scrollTop
3069:                 || 0;
3070:   },
3071: 

3061行目からはPosition.prepare()です。

Position.withinIncludingScrolloffsets()がここで設定しているdeltaX, deltaYを参照しているので,関数を呼び出す前にprepare()を呼んで設定しておく必要があります。

求めたいのはページ自体のスクロール量なのですが,Mozilla, SafariなどではpageXOffsetが使えます。IEでは標準準拠モードでdocument.documentElement.scrollLeftが,後方互換モードでdocument.body.scrollLeftが使えます。これらのうちいずれかがdeltaXとなります。

deltaYの方も同様です。

3072:   realOffset: function(element) {
3073:     var valueT = 0, valueL = 0;
3074:     do {
3075:       valueT += element.scrollTop  || 0;
3076:       valueL += element.scrollLeft || 0;
3077:       element = element.parentNode;
3078:     } while (element);
3079:     return [valueL, valueT];
3080:   },
3081: 

3072行目からはrealOffset()です。積み重なったスクロール可能なオブジェクトの階層を遡って,すべてのスクロール量を積算して [X, Y] の配列で返します。

対象となる要素から,頂点のdocumentまで遡り,各々のscrollTop, scrollLeftを合計しています。

著者プロフィール

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

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