ppBlogでは、不十分ですけどCSS3対応のセレクター関数を実装していたんですが、これを大幅に書き換えました。従来は、XPathが使えるブラウザであれば、CSS3のセレクタをXPath用に変換して、意中の要素を取り出したりしていました(スピード面でなかり有利)。でも、document.querySelectorAllをサポートするブラウザが当たり前になってきたので、少なくともppBlogでは、XPathは不要という結論に達しました(今やFirefoxのためだけに用意してる感じで、しかも次期v3.5では間違いなくquerySelectorAllがサポートされるでしょうから)。
ppBlogで使っている要素抽出のためのセレクターは、大体以下のような感じです。
- o("#ID") 系 // IDを持つ要素を取り出す
- o(".className") 系 // 特定のクラス名を持つ要素を取り出す
- o("a[rel=edit]") // 属性セレクター系。A要素でrel="edit"のやつを取り出す
- o("textarea[id^=Page]") // 属性セレクター系。テキストエリア要素でID名がPageで始まる要素を取り出す
とまぁ、こんな感じで、ほぼ頻度順になってて、圧倒的にID名抽出が多いです。なので、別に高機能なセレクター関数は必要ないわけで。XPathへの変換にえらくファイル容量を割いていたんですが、それをばっさり削ったんで、だいぶシェイプアップされました(oParts.js)。
ただ、属性セレクター(上のリストの下2つ)は、今後、色々使い道がありそうなので、これはスピードも考慮した関数を実装しました。以下のような関数です。正規表現を使っていないので、速度面で有利です。面白いことに、FirefoxよりIE6-8の方が高速に動きます。別に詳しく計測した訳ではありませんが、属性セレクターに限定すれば最速の部類ではないかと思っています(XPathやquerySelectorAllを使わないレガシーな系で)。
// cache = {}; // キャッシュ使うなら
function queryByAttributeSelector(rule){
var apos = rule.indexOf('[');
var tag = rule.slice(0, apos); // タグ名を抽出。
var attr = rule.slice(apos + 1, -1); // 属性を抽出
attr = (attr.indexOf('][') > 0) ? attr.split('][') : [attr];
var s = '', e, k, ks, v, z, i = 0, l = attr.length;
// if(!cache[rule]){ // キャッシュ使うなら
for(; a = attr[i++];){
e = (i != l ? ' && ' : '');
z = a.split('=');
if(z[1]){
k = z[0], ks = k.slice(-1);
v = z[1].indexOf('"')===0 ? z[1].slice(1, -1) : z[1];
k = k.indexOf('class') !== -1 ? k.replace('class', 'className') : k;
switch (ks){
case '^' : k = k.slice(0, -1); s += 'n["'+k+'"].indexOf("'+v+'")===0' + e; break;
case '$' : k = k.slice(0, -1); s += 'n["'+k+'"].lastIndexOf("'+v+'")===(n["'+k+'"].length - "'+v+'".length)' + e; break;
case '~' : k = k.slice(0, -1); s += '(n["'+k+'"]=="'+v+'" || n["'+k+'"].indexOf("'+v+' ")===0 || n["'+k+'"].indexOf(" '+v+' ")!==-1 || ((z=n["'+k+'"].indexOf(" '+v+'"))>0 && !n["'+k+'"].charAt(z+"'+v+'".length+1)))' + e; break;
case '*' : k = k.slice(0, -1); s += 'n["'+k+'"] && n["'+k+'"].indexOf("'+v+'")!=-1' + e; break;
case '|' : k = k.slice(0, -1); s += '(n["'+k+'"]=="'+v+'" || n["'+k+'"].indexOf("'+v+'-")!= -1)' + e; break;
default : s += 'n["'+k+'"]=="'+ v +'"' + e;
}
} else {
s += 'n["'+(a==='class'?'className':a)+'"]' + e;
}
}
/*cache[rule] = s; // キャッシュ使うなら
} else {
s = cache[rule];
}*/
var r = [];
var items = document.getElementsByTagName(tag||'*'), j = 0, n;
var v = new Function('n', 'return ('+s+')');
for (;n = items[j++];){
if(v(n)){
r[r.length] = n;
}
}
return r;
}
工夫した点は、indexOfやらlastIndexOfの積極的活用です。split関数は、正規表現を用いない文字列での分割は、用いる場合より何倍も高速です。後、細かい速度の改善については、uupaaさん
のエントリ
[latest log]が非常に参考になりました。
CSS3の「属性」セレクターをほぼ網羅していると思いますが、とりあえずhttp://mootools.net/slickspeed/
にある属性セレクターはすべてパスします(ただし、div[class!=made_up]は、CSS3にはなさそうなので未対応)。
上のソースコメントをみれば分かりますが、キャッシュを有効にするとより速くなります。でも別になくても良いかなと思ってます。細かい点を言えば、A要素のREL属性なんてのは、大文字小文字を区別しないようですが、そこまでは追ってません。要は、ppBlogで動けば良いので。
ppBlog1.8.0での実装は、上のソースをベースにしたものになっています。
















Comments