Yahoo UI Library Dom.getElementsByClassNameを使ってはまること
CSSクラス指向Javascriptingをしている
例えば、CSSのクラス名'button'があるエレメントにはButtonウィジェットの機能をつけて、加えて'toggle'というクラス名も持っていたらトグルボタンウィジェットにする。
みたいなことを頻繁にやっている。
CSSのクラス名で制御しているのでJavascriptでonXXXとか書かなくても簡単にウィジェット機能を有効にでき、CSS書ける人に教えやすい?のが特徴かなと勝手に思っている。
IEにはdocument.getElementsByClassNameがない
IEには実装されていないので、クラス名ベースで扱うのが面倒でした。まずはJavaScript-XPathを使ってXPathベースで作業を進めていたところ、すごく遅いことがわかった。XPathがそもそもNativeに実装されていないものなので当たり前ですね・・・はい
YahooUIのDom.getElementsByClassNameの方が快適に使えたのでそっちを使うことにした。
Firefoxでは・・・
しかしながら、XPathはめちゃくちゃ早い。Native実装のgetElementsByClassNameよりも早い。そこでXPathが使えるものではXPathでクラス名を検索することにした。
Dom.getElementsByClassNameのワナというよりは自分がアホだった
リファレンスを見ると最後に引数にコールバック関数を指定できる。そのコールバックの中である関数を実行すると同じエレメントが複数回列挙されてしまうのだ。
この問題にしばらくはまっていて、おかしくなるタイミングをデバッガを使いながら調べていた。
問題はとても単純で簡単な話だった。途中で変数が壊れて配列がおかしくなるのかと思っていたが、そうではなくてコールバック関数内でDom操作をしたために、(下のソース中の)elementsが変化してインデックスがずれることが原因だった。
YUIのDom.jsのソース
getElementsByClassName: function(className, tag, root, apply) { className = lang.trim(className); tag = tag || '*'; root = (root) ? Y.Dom.get(root) : null || document; if (!root) { return []; } var nodes = [], elements = root.getElementsByTagName(tag), re = getClassRegEx(className); for (var i = 0, len = elements.length; i < len; ++i) { if ( re.test(elements[i].className) ) { nodes[nodes.length] = elements[i]; if (apply) { apply.call(elements[i], elements[i]); } } } return nodes; }
おかしくなったときのコールバック関数の処理の例(YUIのdragdrop.jsの一部)
var div = document.createElement('div'); div.innerHTML = 'hogehoge'; document.body.insertBefore(div, document.body.firstChild)
まとめ
YUIなどの高級な機能を持ったJavascriptのライブラリを使うと内部で知らないうちにDOM操作を行っていることがあるので、このようなコールバックにそのような処理を書かないようにすべき。
でも気になるのはelementsがDOMでエレメントを追加したときに変化するのがよくわからない。暗黙的に結果が更新されてるってこと?
自分だけがはまっていることだったら恥ずかしいな;;;