prototype.jsを読み解く

第7回 Prototypeライブラリ(2052~2276行目)

この記事を読むのに必要な時間:およそ 10 分

Prototype 1.6.0 RC1

次期バージョン1.6.0のためのリリース候補1.6.0 RC1が出ています。

前回のRC0から着実にバグを修正してきているようです。Event まわりなど,ごっそりと書き換えられている部分もあり,この連載のコードがそのまま残っていない所も出てきてしまっていますが,連載を一通り読んでいる方なら,新しいコードを自ら読み解くこともできるようになっているかと思います。:-)

今回は巨大な Selector クラスです。これも二分割でお送りします。

Selector クラス

500行を超える大物クラスです。

CSSのセレクタに基づく要素のマッチ機能を提供します。CSS3の仕様に含まれるものも実装されていますので,そちらも参考にしてください。

2052: /* Portions of the Selector class are derived from Jack Slocum's DomQuery,
2053:  * part of YUI-Ext version 0.40, distributed under the terms of an MIT-style
2054:  * license.  Please see http://www.yui-ext.com/ for more information. */
2055: 
2056: var Selector = Class.create();
2057: 
2058: Selector.prototype = {
2059:   initialize: function(expression) {
2060:     this.expression = expression.strip();
2061:     this.compileMatcher();
2062:   },
2063: 

コンストラクタです。使い方としては,var s = new Selector("div.header");という形でCSSセレクタ文字列を指定し,var elems = s.match(element)でマッチする要素の配列を返します。

コンストラクタでは,渡されたCSSセレクタ文字列をインスタンス内に保存し,マッチさせるコードを事前にコンパイル(というほどのものでもないのですが)するためにcompileMatcher()を呼び出しています。

2064:   compileMatcher: function() {
2065:     // Selectors with namespaced attributes can't use the XPath version
2066:     if (Prototype.BrowserFeatures.XPath && !(/\[[\w-]*?:/).test(this.expression))
2067:       return this.compileXPathMatcher();
2068: 
2069:     var e = this.expression, ps = Selector.patterns, h = Selector.handlers,
2070:         c = Selector.criteria, le, p, m;
2071: 
2072:     if (Selector._cache[e]) {
2073:       this.matcher = Selector._cache[e]; return;
2074:     }
2075:     this.matcher = ["this.matcher = function(root) {",
2076:                     "var r = root, h = Selector.handlers, c = false, n;"];
2077: 
2078:     while (e && le != e && (/\S/).test(e)) {
2079:       le = e;
2080:       for (var i in ps) {
2081:         p = ps[i];
2082:         if (m = e.match(p)) {
2083:           this.matcher.push(typeof c[i] == 'function' ? c[i](m) :
2084:               new Template(c[i]).evaluate(m));
2085:           e = e.replace(m[0], '');
2086:           break;
2087:         }
2088:       }
2089:     }
2090: 
2091:     this.matcher.push("return h.unique(n);\n}");
2092:     eval(this.matcher.join('\n'));
2093:     Selector._cache[this.expression] = this.matcher;
2094:   },
2095: 

コンストラクタから呼び出されるcompileMatcher()です。Selectorクラス内では,自前のJavaScriptによるCSSクラスによる検索コードと,ブラウザのXPathを使ったものとの二種類が用意されています。XPathを使う方が速いので,可能ならそちらを使いますが,条件によってはJavaScriptによるものも使われます。

まず,ネームスペース属性が使われていると,ブラウザ内蔵のXPathエンジンではうまく扱えないようなので,その場合はJavaScriptコードを使います。またそもそもXPathが実装されていないブラウザの場合もJavaScript版を使います(最近のブラウザでは,IEすべてとSafariの2以前が使えません)。

XPathが使える場合はcompileXPathMatcher()を使って事前準備を行いそのまま帰ります。この関数の2069行目以降はJavaScript版の実装部分です。

変数名が長い物が多いので,短い名前に代入しています。eはCSSフィルタ文字列,psはSelector.patternsに入っているCSSフィルタにマッチさせるための正規表現が入ったオブジェクト,hは様々なサポート関数が入ったオブジェクト(ですが,結局使われていないようです),cがSelector.patternsで見つかった構成要素に対して,マッチ関数がどういうアクションを取るべきか,を記述したオブジェクト,となっています。

この関数の最後で,CSSフィルタ文字列に対してのマッチ関数,という対応で_cacheという変数にキャッシュとして代入しています。もし以前と同じフィルタ文字列が来た場合には,このキャッシュからマッチ関数を再利用できます。

2075行目から,マッチ関数の組立てです。最初this.matcherには文字列の配列が入ります。これから徐々にそこにコード文字列をpush()していって,最後にjoin("\n")してeval()した結果,最終的にはthis.matcherには関数オブジェクトが入ります。

2078行目からのwhileループは,CSSフィルタ文字列eの先頭から,パースできる部分を処理しては削っていく,削りようがなくなったら抜ける,という形になっています。

ループ内では,何も変化していない(先に進んでいない)ことをチェックする為にle = eしています(leはおそらくlast expressionですね)。その後,ps内の全てのプロパティでさらにループしています。

psであるSelector.patternsは2260行目から定義されていて,tagName, id, classNameなど,フィルタの構成要素をプロパティ名にもち,それにマッチするための正規表現オブジェクトがプロパティ値に入っています。

といことで,2081行目の段階で,iにはフィルタの構成要素名,pには正規表現オブジェクトが入っています。

そして,現在処理中のCSSフィルタ文字列eに対して match(p)を行い,マッチするようならcに入っている処理内容をmatcherに追加して,eから今回マッチした部分を取り除き,for文をbreakしてwhileループに戻ります。

ここで,2083行目の処理からわかるように,c (Selector.criteria)の中身は関数オブジェクトか,Templateクラスで処理される文字列,となっています。

whileループが終わると,matcherの最後に返り値の為の部分を追加し,それをjoin("\n")して単一文字列にした上でeval()します。matcherの先頭には this.matcher = function(root) { という部分がありました。これでthis.matcherが関数オブジェクトとして上書きされます。

最後に,Selector._cacheにキャッシュさせて終了です。

著者プロフィール

栗山淳(くりやまじゅん)

S2ファクトリー株式会社株式会社イメージソース所属。
本業はWeb制作会社の裏方。得意分野はFreeBSDやPerlのはずだが,必要に迫られるとHTML/CSSやJavaScriptも書く。

コメント

コメントの記入