2010/03/05

getElementsByClassName()の落とし穴

こんにちは。新規事業推進室の石田です。

 

HTMLで、特定のクラス名を持つDOMエレメントを列挙してなにか操作をしたいときに、prototype.jsにあるgetElementsByClassName()を使うと探すエレメントを探す手間を省いてくれます。

で、このprototype.jsのgetElementsByClassName()は、エレメントを列挙してクラス名をチェックするという実装になっているので、当然遅いです。

 

そこで、Firefox3やChrome、Safariは、このgetElementsByClassName()がネイティブ実装されるようになりました。prototype.jsでもネイティブ実装があればそちらを優先するという風になってます。

ところが、このネイティブ実装版のgetElementsByClassName()は結果として返す配列のエレメントのクラス名を変更すると配列自体が変更されてしまうという挙動を示します。

 

わかりにくいと思うので、サンプルのHTMLを用意しました。

以下のHTMLですが、testというクラス名を持つdivが3つあって、ボタンを押すとそのdivのクラス名をtestからchangeに変更し、文字の色を赤くするという動作を期待して作りました。

 

 

これをIEで表示すると prototype.js の getElementsByClassName() をつかうことになるので期待したとおりに、壱弐参の文字が赤くなります。

このHTMLを Firefox3 で表示した場合は、ボタンのクリックで弐だけが赤くなり、JavaScriptのエラーが報告されます。

さて、何が起きているのでしょうか?

 

どうやらFirefox3では、ループの中の、elements[i].className = “change”; を実行した時点で、elements の配列の中からこのエレメントがなくなってしまうようです。

ゲゲッ!

 

そんな仕様ないだろうと思って、HTML5のWorking Draft仕様を読んでみると確かに When called, the method must return a live NodeList object と書いてある。

えー、ありえないでしょう。

http://www.whatwg.org/specs/web-apps/current-work/#dom-document-getelementsbyclassname

というわけで上記のコードは正しくは、

と、一旦配列をコピーしてからクラス名の変更を行う必要があります。

これ、知らなかったのは私だけでしょうか・・・ひょっとして常識?

 

では。