querySelectorとquerySelectorAllというかLive NodeListとStatic NodeList

先日、getElementsByClassName便利だぜ!とブログに書いたら、to-R の西畑先生よりquerySelectorAllアルヨ!と言われたので、調べてみる。

querySelector と querySelectorAll

まぁ上記エントリにほぼ全てが書かれているので、特に今さら書くことはないのですが、自分メモのために。

なにはともあれ、サポート状況をば。

Fx3.0 は対応していないけど、IE8 は対応してるのか。素敵!

element = document.querySelector(selectors);

querySelectorってのもある。基本的な使い方は両方一緒でselectors</emの引数に、取得したい要素の CSS セレクタ書いてあげればいい。querySelectorは最初に見つけてきた単一の要素を返すのに対して、

elementList = document.querySelectorAll(selectors);

elementList is a non-live NodeList of element objects.

querySelectorAll はノードリストを返す。

var node = document.querySelectorAll("#hoge > h2");

つまり、#hogeの中の子供の h2 だけを取ってくるなんてことも、上記のように CSS セレクタで簡単に書けちゃう。jQuery ライクに書けちゃう。だから、これまで Firefox のグリモンとか Chrome の拡張機能を作成するときは僕は jQuery 読み込んでいたんだけど、簡単なものであれば Selectors API 使えば、jQuery に頼らなくても良くなった。

Live NodeList と Static NodeList

はい、そんなわけで Selectors API\(-o-)/なんですけども、ひとつ気になる点がありました。querySelectorAllが返すのは non-live なノードリストと書いてあります。non-live って何よ?ってことで、仕様書、仕様書

querySelectorAll() メソッドから返される NodeList オブジェクトは 動的 (live) ではなく、静的 (static) である必要があります ([DOM-LEVEL-3-CORE], section 1.1.1) (must)。元文書の構造が変化しても、その変化が NodeList オブジェクトに反映されることは許されていません (must not)。つまり、返されるオブジェクトは、リストが生成された時点で文書に存在していたノードに対しクエリをかけ、マッチする Element ノードを取得することを意味します。

静的なノードリストなわけですね。具体的な例だと、

var divs = document.getElementsByTagName("div"),
  i = 0;
while (i > divs.length) {
  document.body.appendChild(document.createElement("div"));
  i++;
}

getElementsByTagName()で返される値は動的なノードリストですので、上記のスクリプトは無限ループになる。

var divs = document.querySelectorAll("div"),
  i = 0;
while (i < divs.length) {
  document.body.appendChild(document.createElement("div"));
  i++;
}

代わって、querySelectorAllで返される値は静的なノードリストで、取得してきた時点での数になります。つまり、div が 10 個そのときあったのであれば、その後に何個 div を生成しようが 10 回でこのループは止まります。

違いが分かりましたけど、なんでちゃうのん?

コメント欄に uupaa せんせが DOM アクセスを減らすためと書いてある。

おーなるほど、パフォーマンスのためか?と思ったのでもうちっと調べてみると、こんな記事があった。

上記ブログに書いてあることをなんとなく理解すると、静的リストはまるまるコピーするから事前にやることが多いので、動的リストよりも遅くなる。けども、取ってきたノードリストをイテレートする分には動的リストは毎回チェックするのに対して、静的リストはしないから速い。とのことみたいな事言ってるんだけど、

jsPerf: JavaScript performance playgroundでパフォーマンスチェックしてみたのが以下のテスト。ついでに、ほかのgetElementsBy*メソッドもテストしてみた。

getElementsByTagName VS querySelectorAll · jsPerf

getElementsByName VS querySelectorAll · jsPerf

getElementsByClassName VS querySelectorAll · jsPerf

なんか全部のテストで、(Opera を除いて)getElementsBy*(動的)が速いんですけど.でも、このテストだと QSA の方が速い…うんーわからん。だれかおせーてエロい人!

結局、getElementsBy*で取れるもんはわざわざ、QSA でやらないほうがいいよ。ってことかな。あ、だからって QSA をディスってないよ。クラスの複数付けとか取ってくるときは QSA でやったほうが楽チンだし速い。と思ったけど、このテストだと getElementsByTagName の方が速いじゃん(High Perfromace JavaScript には QSA のほうが 2-6 倍速いって書いてあるのに…)

とりあえず、よく分かりません!