XPathとSelectorsAPIとgetElementsBy〜の使い分け
微妙に役割の被っている、document.evaluateとSelectorsAPIを、どう使い分けるかという話。
僕は最近、
- タグ名、クラス名、idで一発で取得できる場合は速度のためにgetElements系のものを使う。
- XPathでできて、SelectorsAPIでできない時は、XPathを使う。
- 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);
としたほうが分かりやすく、高速。