Prototype 1.6.0 で大きく進化したイベント API
Prototype 1.6.0 RC にはいくつもの変更点がある。
特に イベント周りの API で顕著だ。Event.observe
メソッドなど、お馴染みの API が大幅に手を加えられている。
Event.observe
で登録したコールバックの this
いままで Event.observe
でコールバック関数を登録するときは、コールバックが呼ばれたときの this
を固定するため(と、引数に event
が確実に渡ってくるように)、
var element = $('mybutton');
Event.observe(element, 'click', onClickEventListener.bindAsEventListener(element));
Function#bindAsEventListener
を使っていたと思う。
これが 1.6.0 では、Event.observe
や Element.observe
で登録したコールバック関数はすべてのブラウザで、this
がイベントの発生した DOM 要素となるように変更された。
そのため、上記のコードはシンプルに、
var element = $('mybutton');
Event.observe(element, 'click', onClickEventListener);
と書けるようになった。
もっとも、コールバック関数を登録するオブジェクト(コントローラー)自身を this
にすることの方が多いかもしれない。その場合は bind
する必要がある。
var element = $('mybutton');
Event.observe(element, 'click', onClickEventListener.bind(this));
コールバック関数の this
を固定する処理
すでに Function#bind
や Function#bindAsEventListener
が紹介されつくしているご時世に今更な感もあるが、Event.observe
で登録したコールバック関数の this
を固定している処理を見てみよう。
observe: function(element, eventName, handler) {
element = $(element);
var id = getEventID(element), name = getDOMEventName(eventName);
var wrapper = createWrapper(id, eventName, handler);
if (!wrapper) return element;
createWrapper
が、渡されたコールバック関数 handler
から this
を固定したラッパー関数を返す。
function createWrapper(id, eventName, handler) {
var c = getWrappersForEventName(id, eventName);
if (c.pluck("handler").include(handler)) return false;
var wrapper = function(event) {
if (event.eventName && event.eventName != eventName)
return false;
Event.extend(event);
handler.call(event.target, event);
};
wrapper.handler = handler;
c.push(wrapper);
return wrapper;
}
強調部分に注目してほしい。Event.extend
でイベントオブジェクトを拡張したうえで、event.target
つまりイベントが発生した DOM 要素のメソッドとしてコールバック関数を呼んでいる。
Object.extend
と静的スコープの利用
ところで、Event
オブジェクトに observe
メソッドを追加するときは Object.extend
によるコピーを利用しているのだが、その利用法が珍しい。
どのようになっているのか? 構造を示すために細かい部分を省略したコードを以下に載せる。
Object.extend(Event, (function() {
function createWrapper(id, eventName, handler) {
...
}
return {
observe: function(element, eventName, handler) {
...
},
...
};
})());
かなり読みづらいが、無名関数の呼び出し結果を Object.extend
に渡している。
Object.extend(Event, (function() {...})());
ここで、
- 実際に
Object.extend
に渡されるのは、無名関数内でreturn
されているオブジェクト - このオブジェクトのメソッドが公開される API として
Object.extend
される - その他の関数はこれらのメソッドから利用されるだけのユーティリティ
というわけだ。
これは、名前空間を汚染しないために無名関数の静的スコープを利用する、という JavaScript のイディオムと同じであることが分かると思う。
(function() {
// この内部で定義した関数や変数は、この無名関数より外部の
// 名前空間を汚染しない。
function doSomething() {
...
}
})();
ただ、そこから return
されるオブジェクトを別の関数の引数に渡す、というやり方ははじめて見た。少なくとも Prototype 1.5.1.1 までは見られなかった手法だ。
長くなってしまった…。Prototype 1.6.0 のイベント API には他にも変更点がある。それらはまた次回。