Prototype 1.6.0 で大きく進化したイベント API

2007/09/02 9:55am

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.observeElement.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#bindFunction#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() {...})());

ここで、

というわけだ。

これは、名前空間を汚染しないために無名関数の静的スコープを利用する、という JavaScript のイディオムと同じであることが分かると思う。

(function() {
  // この内部で定義した関数や変数は、この無名関数より外部の
  // 名前空間を汚染しない。
  function doSomething() {
    ...
  }
})();

ただ、そこから return されるオブジェクトを別の関数の引数に渡す、というやり方ははじめて見た。少なくとも Prototype 1.5.1.1 までは見られなかった手法だ。

長くなってしまった…。Prototype 1.6.0 のイベント API には他にも変更点がある。それらはまた次回。