愛と勇気と缶ビール

ふしぎとぼくらはなにをしたらよいか

mixiをしていたごく普通のOLが、何故NodeListというフィールドに舞い降りたのか?

エントリのタイトルを考えるのが面倒になったので、ホッテントリメーカーで作成もしくはホッテントリメーカーで作成されたかのようなタイトルをつけることにしました。


getElementHogeHogeが返すのはliveなNodeListで、querySelectorHogeHogeが返すのはstaticなNodeListだ、というのは既によく知られている。

(参考: http://d.hatena.ne.jp/amachang/20080306/1204787459, http://web.g.hatena.ne.jp/vantguarde/?of=90)

会社の人にこのことを話していたら、「内部的なアレがそれぞれ違うのはいいとして、Array.prototype.slice.callとかでArrayに変換した場合はどうなるの?」と聞かれて「ウーン・・・」となって答えられなかったので、今から30秒で検証コードを書きます。


...


はい、既に作ってあったコードがこちらになります。

(gist) https://gist.github.com/1924408
(jsdo.it) http://jsdo.it/zentooo/lqUD/fullscreen

ちなみにconsole.logで結果を出しているので、console.logとかが出る環境じゃないと何も出ないです。

コードの意図としては、「DOMContentLoadedの時点でNodeList, Arrayに変換したNodeList, (Static)NodeList, Arrayに変換した(Static)NodeListを用意しておき、それから要素クリックで色々DOMに変更加えたあと下のボタンでダンプ」とかそんな感じです。何だか分かりにくいですが、ボタンとか上のaとかbとかの要素をポチポチクリッコしてみれば大体分かるかと。

ちなみに、各Elementをクリックすると発動するDOMの変更(各グループについて上から 1.自身をDOM Treeから削除、2.自身のクローンを親要素に追加、3.自身のクローンを自身に追加)が、どのようにダンプ結果に影響を与えるか事前に全部分かった人はいるでしょうか?僕はなんとなくの予想はしていましたが、結構ズレてました。


そもそもInterface NodeListは、DOM 3 Coreでは

http://www.w3.org/TR/DOM-Level-3-Core/core.html#ID-536297177

こんな感じで、そこで言ってる"live"とはなんぞや、とリンクを辿ると

http://www.w3.org/TR/DOM-Level-3-Core/core.html#td-live

こんなことが書いてある。「あなたがあるElementの子要素をもつNodeListをゲッツしたとしよう。そのElementに新たな要素を追加したり、または要素をそこから削除したり、要素を変更した場合NodeListにそれらの変更は自動的に反映される。同じように、DOM Tree内のNodeに対する全ての変更は、NodeList内のNodeに反映される」


…お分かり、頂けただろうか?

いや、これ自体の意味は分かるのだけど、

http://www.w3.org/TR/selectors-api/

上記リンク先のように、「staticなNodeListってのは要はliveじゃないNodeListだよ〜ん」と書かれると何だかよくわかんなくなってしまう。実際のあるべき挙動は実はその後にある程度ちゃんと書いてあるんだけど、"static = not live = liveの満たす条件は全部満たさない" って解釈だと、「staticなNodeListではDOMへの変更がNodeListに反映されないばかりか、NodeList内の各Elementにも反映されないのー!?ウッソーン!」となってしまう。

もちろん実際にはそんなことはなくて、DOMを変更した(例えば、当該Elementに何かをappendChildした)場合にはその変更はNodeListがliveだろうがstaticだろうがNodeListの中身のElementに反映される。というかそうじゃないとプログラムが書きにくくてしょうがない。

要は、「DOMの変更がNodeListに反映されるか否か ≠ DOMの変更がNodeList内のElementに反映されるか否か」だということ。ちょっと考えてみれば分かることだけど、紛らわしい。


オマケついでに答え合わせをすると、

1) document.getElementsByClassName("a")で得たliveなNodeListは、documentにclass=aなElementが追加されると追随して要素が一つ増える。

2) document.getElementsByClassName("b")で得たliveなNodeListをArray.prototype.slice.callでArrayに変換すると、documentにclass=bなElementが追加されてもそのArrayの要素は増えない(これも増えたら気持ち悪い)

3) document.querySelectorAll(".c")で得たstaticなNodeListは、documentにclass=cなElementが追加されても要素は増えない

4) document.querySelectorAll(".d")で得たstaticなNodeListをArray(ry でArrayに変換すると、当然ながらdocumentにclass=dなElementが追加されても要素は増えない

という感じになります。いやしくもJavaScriptをぴよぴよしている人間としてFirefoxとChromeでしか調べていない、というのはありうべからざる怠慢のようですが、これは怠慢ではなく読者の方に「blog等を読んでなんとなく納得したような気分にならず、自分で手を動かして確かめる」という未曾有のチャンスを提供しているだけなのです。



はいウソです。



これらの動作って、getElementHogeHogeもquerySelectorHogeHogeも全部DOMに対する「クエリ」と見なした上で

liveなNodeList -> 元のデータセットに変更があると、クエリの結果セットとしてのNodeListはクエリに従って変化する
staticなNodeList -> 元のデータセットに変更があっても、クエリの結果セットとしてのNodeListは変化しない

という捉え方をした方がわかりやすいかと思ったけど、どうじゃろうか。逆に分かりにくいか。てへぺろ。