DOMのEventsは遅い?
DOMのイベントはノードが深くなればなるほど処理に時間がかかるという不思議なスペックになっています。キャンセレーションの機能はあるものの、イベントがノードにディスパッチされた時点で、ディスパッチ処理がドキュメントからそのノードへ遷移 していくキャプチャフェーズの処理はどうしても避けることができません。つまりノードからparentNodeをn回くりかえしてドキュメントまで辿りつく処理は避けられないのです。もちろんclickとかmousedown程度では気にするほどの処理ではないのですが、たとえばmousemoveを処理しようとすると、マウスを動かすたびにイベントをディスパッチして例のキャプチャフェーズに加えて付加的にですがバブルフェーズまでもが実行されます。これの気持ち悪いところは先にも言ったとおりノードが深くなるにつれて一個のイベントの処理にかかる時間も増加するというところにあります。ちなみにHTMLのmousemoveはcancelableです。たぶんそのことを考慮したのだと思います。
それで、先日そのことが少し問題になったので対策を考えてみました。避けられないキャプチャフェーズの遷移をなんとかする対策。おそらくこれの根本的な対策はひとつしかなくて、つまりイベントをディスパッチしないという方法です。例えば
node.dispatchEvent(evt);
と確実にディスパッチしているところを、
if (!node.getAttribute(evt.getType() + "Event").equals("false")) { node.dispatchEvent(evt); }
として、mousemoveEvent="false"というアトリビュートを追加しておきます(node implements Element, EventTarget)。getAttributeの処理が多少重そうですが、これはおよそ一定時間で終了するのでよしとしましょう。ただこの対策は最終手段です。可能ならば
document.addEventListener('mousemove', function (e) { e.stopPropagation(); }, true);
として、できるだけイベントハンドリングの処理を省いてしまう方法を使うほうがいくらか健全です。どちらにしても強引なことには変わりありませんが。あとcancelableじゃないイベントは効果ないので、手当たり次第cancelableなイベントとして実装してしまうか、
function EventCanceller(e) { e.initEvent(e.type, false, true): e.stopPropagation(); } document.addEventListener('mousemove', EventCanceller, true);
のように無理矢理cancelableにしてからキャンセルしてしまいましょう。もちろんこの方法は実装に依存します。が、普通の実装をしていれば動くはずです(DHTMLじゃうごかなさそうだけど)。ちなみにinitEvent系は二回以上呼べます(もちろん実装依存)。これに関連したもうすこし現実的な対策は、documentにaddEventListenerするのではなくて、ノードを保持する構造としてのノードを適当に選びだして、同様なキャンセル処理をaddEventListenerでいれてしまいます。もしくは全然違う方法として(かつ根本的な解決),Eventの中にcaapturableという独自の仕様を作ってしまって、これがtrueの場合にのみキャプチャフェーズを開始するようにするなど。
まあ実装を自由にいじくれるので時間さえあればなんとかなるのですが、ディスパッチをそもそも無視してしまうのはキャプチャフェーズは確実に起こるというDOM Eventsの仕様に反するような気もします(この際全然気にしないのですが)。DOM Coreは割としっかりしている印象がありますが(NS系はすごい気持ち悪いですが)、DOM Eventsは全体においてしょぼいような気がするのです。いまだにKeyEventの仕様ないし。
結局のところ有効な対策としては、そもそもイベントをディスパッチしないようにする、キャプチャフェーズの面倒をあれこれ見てやる、getElementByIdとかgetAttributeとかchildNodesとかつまりDOMの全体的な機能を改良する(間接的なのですがリスナ側から使うのは確実なので。それにたぶん今回問題になったのはここ)、基本的にパフォーマンスの高い言語を選択する、DOMを捨てる(あるいはDOM Events)、なのかなあと思うのです。つまるところ、普通に考えればDOMNodeInsertedIntoDocumentとかDOMAttrModifiedの処理が頻繁に起こりすぎて普通の実装ではすぐにガタがきてしまうのは必至なのですが、大したことないだろうと楽観視していて痛い目にあったというそういう話です。そういう経験をしているのでDHTMLレンダリングエンジンはキャッシュとかアルゴリズムを駆使してできるだけ重くならないように工夫しているのだろうと容易に想像できるわけです。
最後にW3Cのリンクを貼っておきます。プログラミングに使う構造の一つとして勉強してみれば、結構ちゃんとした仕様になっていることがわかると思います。今回の仕事はかなり失敗気味ですが(もちろんなんとかしますが)、DOM CoreとかDOM Eventsに関してかなり詳しくなったので、これはこれで勝手によしとしています(今ぐらいDOMに詳しければ最初からAjax使ってたと思いますが、実際知らなかったので仕方ありません)。個人的にはhasFeatureとかcreate*とかimportNodeとか、かなりアイデアが得られました(Core 3はどんどん煩雑になっている感じをうけますが)。
- The URL to Trackback this entry is:
- http://dev.ariel-networks.com/Members/matsuyama/dom-events/tbping