素人がプログラミングを勉強していたブログ

プログラミング、セキュリティ、英語、Webなどのブログ since 2008

連絡先: twitter: @javascripter にどうぞ。

XPathとSelectorsAPIとgetElementsBy〜の使い分け

微妙に役割の被っている、document.evaluateとSelectorsAPIを、どう使い分けるかという話。
僕は最近、

  1. タグ名、クラス名、idで一発で取得できる場合は速度のためにgetElements系のものを使う。
  2. XPathでできて、SelectorsAPIでできない時は、XPathを使う。
  3. XPathでもSelectorsAPIでも出来るときは、特別な理由がない限りSelectorsAPIを使う。

のような感じで使い分けている。

クラス名を扱う時はSelectorsAPIを使う。
例えば、はてなキーワードを普通のリンクにするJavaScriptは、XPathを使うと

var keywords = document.evaluate("descendant::a[contains(concat(' ',@class,' '),'keyword') or contains(concat(' ',@class,' '),'okeyword')]",
  document.body,
  null,
  XPathResult.ORDERED_NODE_SNAPSHOT_TYPE,
  null);
for (var i = 0;i < keywords.snapshotLength;i++) {
  var keyword = keywords.snapshotItem(i);
  keyword.parentNode.replaceChild(keyword.firstChild, keyword);
}

のように、複雑なものになってしまうが、SelectorsAPIの場合、

Array.forEach(
  document.body.querySelectorAll(".keyword,.okeyword"),
  function(elem) { elem.parentNode.replaceChild(elem.firstChild, elem) }
);

のように非常にシンプルにできる。
この場合、getElementsByClassNameを使うこともできるが、複数のクラス名に対して処理をするために、forEachとかを使わなければならない所が少し格好悪い。
また、配列に重複した要素が含まれる可能性があるので、複数のクラス名から取得するために

Array.slice(document.getElementsByClassName("keyword")).concat(Array.slice(document.getElementsByClassName("okeyword")));

とすることはできない。重複を弾くコードを追加するよりは、素直にSelectorsAPIを使った方が高速でわかりやすい。

ほかにも、CSSの:checkedなどにあたるものをXPathから取得する場合、うまくいかないことがあるので( XPathの"@checked"と CSS2の":checked", setAttribute()は控えめに。 - latest log)、そういう場合もSelectorsAPIを使うといい。
XPathを使った方がいい場合は、子要素の状態に応じて要素を取得したい場合や、なかのテキストを使って要素を取得したい場合。
例えば、中に画像を含むa要素を取得したい場合、
SelectorsAPIを使って

Array.map(document.querySelectorAll("a img"), function(elem) elem.parentNode);

とするより、
XPathを使って

document.evaluate(
  "descendant::a[descendant::img]",
  document,
  null,
  XPathResult.ORDERED_NODE_SNAPSHOT_TYPE,
  null);

としたほうが分かりやすく、高速。