querySelectorAll相当の機能を実装してみた(uupaa.jsのCSSセレクタを書き直した)

ここ数日は、uupaa.jsCSSセレクタ部分を書き直してました。
CSSセレクタ部分とは、jQuery("div > p") とか $$("#hoge~span") を解釈し要素を選択する機能です。document.querySelectorAll() としても標準化されています。

uupaa.js(ver 0.5)まではCSSセレクタXPath式に変換する方法で一応CSS3セレクタもサポートしていたのですが、XPathではできないことが結構ありまして、かなりダメポな実装になっていました。
とまぁ、時間的な理由で書き直せずにくすぶっていたのですが、(IE5で)最速セレクターを作ってみた - ?D of K で紹介されたことで、書き直すきっかけをいただきました。id:ofkさんに感謝。

uupaa.js(ver 0.6)のCSSセレクタは、新しい実装に置き換わります。


CSS3.info のテスト用のHTMLをお借りして、各ライブラリ毎のCSSセレクタの比較もしてみました。

比較

passがテストをパスした数で, testが全テスト項目の件数。pointが合格率。

IE6 and IE7

Lib pass/test point
jQuery 379/580 65%
kQuery 545/580 94%
Prototype.js 538/580 93%
uupaa.js 566/580 98%

Opera9.27

Lib pass/test point
jQuery 384/580 66%
kQuery 552/580 95%
Prototype.js 538/580 93%
uupaa.js 563/580 97%

Opera9.6β

Lib pass/test point
jQuery 387/580 67%
kQuery 555/580 96%
Prototype.js 485/580 84%
uupaa.js 570/580 98%

Firefox2.0.16 and Firefox3.0.1

Lib pass/test point
jQuery 391/580 67%
kQuery 559/580 96%
Prototype.js 548/580 94%
uupaa.js 570/580 98%

Safari3.1 and Google Chrome

Lib pass/test point
jQuery 387/580 67%
kQuery 263/580 45%
Prototype.js 545/580 94%
uupaa.js 580/580 100%

jQuery(1.2.6), Prototype.js(1.6.0), uupaa.js(0.6 RC0)
なお、kQuery はリリースされていないようなので、javascripterさんのブックマークからたどって秘密裏に入手しました(w

uupaa.js が97%しか得点できない理由 とか 制限事項とか

querySelectorAllでは擬似要素を取得できないことになっているのと、:visited はセキュリティの都合で実装していません。not も実装していません。

  • :link で訪問済みのリンクかどうか区別していない(-1点)
  • :visited はセキュリティの関係で実装していない(-1点)
  • :before, :after, :first-letter, :first-line はDOM要素と1対1で対応していないため、正攻法では実装できない(-4点)
    • ::before, ::after, ::first-letter, ::first-line も同様(-4点)
  • :first-of-type, :last-of-type の一部解釈が仕様と異なる(-2点)解決済み
  • :not() 未実装(-2点)

超えられなかった壁

壁というか 作業中に遭遇したブラウザのバグ等。

  • Opera9.2x には二つのバグがある。これはOpera9.5で修正されている。
    • <div align="leftorright"> や <div align=" left "> を元に <div align="left"> といった(改ざんした)DOMツリーを構築するらしく、div[align=left] や div[align$=left]と不正にマッチしてしまう。
      • このバグを回避する方法がないためOpera9.2xでは、pointが6点減っている。
    • <div><!-- Just a comment --></div> の div.innerText は "" が返るのに、div.textContent からはコメントの中身 " Just a comment "が返る。
      • このバグはOpera9.2xならinnerTextを参照するようにして回避している。
  • IEではDOMツリーに空白や改行だけのテキストノードが含まれないため、:empty が <div> </div> にマッチしてしまう。 -1点
  • IE7以下にはhasAttributeがないため、E[attribute] で本来マッチしない要素もマッチしてしまう。 -2点
    • こんなコードでなんとかpointを稼いだが完全解決はできなかった。
function hasAttribute(node, attrName) {
  if (uu.ua.ie && !uu.ua.ie8) {
    var attr = node.getAttributeNode(attrName);
    return attr && attr.specified;
  }
  return node.hasAttribute(attrName);
}

反省会

  • jQueryは65点の赤点すれすれなのに、みんなが大好き。謎だ。
    • あ。あれか「セレクタの性能の違いが戦力の決定的…」(by 3倍の人) とか、そういうやつか。
    • ということは、CSS3セレクタの仕様がオーバースペックなのであって、一般的な利用ではjQuery程度の実装で間に合っているということか。
      • xx-of-type とか :not とか、無くても困らんしなぁ。
  • CSSセレクタ部分だけスピンオフ可能だけど、ネット上はみんなjQuery大好きっ子ばっかりで需要がなさげだ。
  • 実際にテストしてみたい方は、http://pigs.sourceforge.jp/blog/200809192139/ からどうぞ
    • 各フォルダの中にある test.html がテスト本体になります。
      • :target は ぱっと見でテストが失敗しているように見えますが、テスト項目を右クリックしてリンクを開き、それだけをテストするとオールグリーンになったりします。(上記の集計でも加味してあります)
  • 自作ライブラリをテストしたい方は、ディレクトリごとダウンロードして、 selector.js の末尾に以下をくっつけると良いでしょう。
if (!("forEach" in Array.prototype)) {
  Array.prototype.forEach=function(fn,me){
    for(var i=0,sz=this.length;i<sz;++i){(i in this)&&fn.call(me,this[i],i,this);}
  };
}
var CSSSelector = $.find; // Prototype.js なら var CSSSelector = $$; にします。
    • どっかまずいところがあったら教えてください。直しますので。


いかんいかん、コレを言い忘れるところだった。

「kQuery 恐るべし」
kQueryが本領を発揮するとすごいことになりそうです。uupaa.js も負けないようにがんばります。

追記

  • 2008-09-21 9/21版で、E:xx-of-typeと、E[A=V]の実装を修正しました。
  • 2008-09-30 uupaa.js version 0.6 で :link, :visited, :hover, :focus をサポートしました。
    • ただし、:hover, :focus は一部のブラウザでしか機能しません。
  • 2008-10-02 nth-childのバグを修正しました。