ブラウザ上でのイベント処理の仕組みは DOM 2 Events および DOM 3 Events 草案にて規定されています。しかし、DOM 2 Events で言及されていない部分など、細かい動作はブラウザごとに異なっていることもあります。そうした仕様と実装の差異を、「作って納得! DOM 2 Events」で触れなかったものも含めて、いくつかまとめてみました。
DOM 2 Events のイベントモデルにおいて、あるノードでイベントが発生すると、そのノードの祖先ノードのイベントリスナが呼び出されるキャプチャリングフェーズ、そのノード自身のイベントリスナが呼び出されるターゲットフェーズ、再び祖先ノードのイベントリスナが呼び出されるバブリングフェーズと、3 段階にわたってイベントが伝播していきます。このうちターゲットフェーズでは、addEventListener
メソッドで第 3 引数 useCapture
を false
に指定して登録したイベントリスナのみが呼び出されることになっています。
Opera 9 はこれに従っていますが、Firefox 2 および Safari 2 ではターゲットフェーズにおいて、useCapture
を true
に指定して登録したイベントリスナまでもが呼び出されてしまいます (参考: Mozilla Bug 235441)。なお、Safari はナイトリービルドにおいて一旦は仕様どおりの動作になったものの、Gecko との互換性を保つためにあえて以前の動作に戻したようです。
DOM 2 Events において、EventListener
インターフェースを実装するオブジェクトは handleEvent
メソッドを実装しなくてはいけません。しかし、これは Java のような、関数をファーストオブジェクトとして扱えない言語を考慮に入れてのことであり、付録の ECMAScript Language Binding によれば、ECMAScript では関数オブジェクトが EventListener
インターフェースを実装することになっています。すなわち、関数を addEventListener
メソッドの第 2 引数 listener
などに直接渡せるということです。
ですが、Firefox 2 や Opera 9 では、関数オブジェクトでなくとも handleEvent
メソッドを持つオブジェクトなら、EventListener インターフェースを実装するものとして扱ってくれます。Safari 2 では関数オブジェクトしか EventListener
として使えませんが、Safari のナイトリービルドでは handleEvent
メソッドを持つオブジェクトも EventListener
として使えるようです。
var listener = {
handleEvent: function (evt) { alert(this); },
toString: function () { return "[EventListener implementation]"; }
};
// Click, and alerts "[EventListener implementation]".
// OK: Firefox 2, Opera 9, and maybe Safari 3
element.addEventListener("click", listener, false);
あるノードでイベントが発生すると、キャプチャリングフェーズではルートノードから直近の親ノードまで、バブリングフェーズでは親ノードからルートノードまでのイベントリスナが呼び出されます。ここで、ルートノードというのは、通常は文書ノード (document
) のことを指します。つまり、ある文書中の要素ノードでイベントが発生した場合、キャプチャリングフェーズは document
から始まり、バブリングフェーズは document
で終わるといった具合です。
セコメントをする