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