prototype.jsを読み解く

第8回 Prototypeライブラリ(2277~2620行目)

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

2450:     pseudo: function(nodes, name, value, root, combinator) {
2451:       if (nodes && combinator) nodes = this[combinator](nodes);
2452:       if (!nodes) nodes = root.getElementsByTagName("*");
2453:       return Selector.pseudos[name](nodes, value, root);
2454:     }
2455:   },
2456: 

2450行目からはpseudo()ハンドラです。CSS3で定義されたfirst-of-typeなどを含めた擬似クラスへの対応です。

事前にcombinatorが使われていた場合は,nodesをそれで絞り込みます。ここでのthisはSelector.handlersとなっています。nodesが空の場合はrootから下の要素をすべて取得しておきます。

あとは,Selector.pseudosオブジェクトにそれぞれの擬似クラスに対応したハンドラが定義されているので,それに渡して返り値をそのまま返します。

2457:   pseudos: {
2458:     'first-child': function(nodes, value, root) {
2459:       for (var i = 0, results = [], node; node = nodes[i]; i++) {
2460:         if (Selector.handlers.previousElementSibling(node)) continue;
2461:           results.push(node);
2462:       }
2463:       return results;
2464:     },

2457行目からは,先ほどのSelector.handlers.pseudo()から呼び出されるSelector.pseudosオブジェクトです。

2458行目からはfirst-childです。この擬似クラス自体はCSS2から存在しますが,このあたりの擬似クラスなどのセレクタはWin IEでの対応状況が悪いので,実際のCSSとしては使わないことが多いでしょう。一方Prototypeライブラリ内では自前で処理を行うので,互換性を気にすることなく使うことができます。

'first-child'は兄弟中の先頭の要素を示すので,渡されたnodes配列をループして,Selector.handlers.previousElementSibling()が偽を返すものをまとめて返しています。

2465:     'last-child': function(nodes, value, root) {
2466:       for (var i = 0, results = [], node; node = nodes[i]; i++) {
2467:         if (Selector.handlers.nextElementSibling(node)) continue;
2468:           results.push(node);
2469:       }
2470:       return results;
2471:     },

2465行目からはlast-childです。こちらはCSS3から登場です。

first-childと同じようにループして,Selector.handlers.previousElementSibling()が偽のものだけを集めて返しています。

2472:     'only-child': function(nodes, value, root) {
2473:       var h = Selector.handlers;
2474:       for (var i = 0, results = [], node; node = nodes[i]; i++)
2475:         if (!h.previousElementSibling(node) && !h.nextElementSibling(node))
2476:           results.push(node);
2477:       return results;
2478:     },

2472行目からはCSS3のonly-child 擬似クラスです。自分が他に兄弟がいない(親の子供が自分しかいない)要素にマッチします。

first-childなどと同様にnodes配列をループさせ,Selector.handlers.previousElementSibling()とSelector.handlers.nextElementSibling()の両方が偽になる(すなわち前にも後ろにも兄弟がいない)ノードを集めて返しています。

2479:     'nth-child':        function(nodes, formula, root) {
2480:       return Selector.pseudos.nth(nodes, formula, root);
2481:     },

2479行目はnth-child擬似クラスです。nth-child(1) や nth-child(2n+1) のように使います。

実際の処理はSelector.pseudos.nth()関数に任せています。

2482:     'nth-last-child':   function(nodes, formula, root) {
2483:       return Selector.pseudos.nth(nodes, formula, root, true);
2484:     },
2485:     'nth-of-type':      function(nodes, formula, root) {
2486:       return Selector.pseudos.nth(nodes, formula, root, false, true);
2487:     },
2488:     'nth-last-of-type': function(nodes, formula, root) {
2489:       return Selector.pseudos.nth(nodes, formula, root, true, true);
2490:     },
2491:     'first-of-type':    function(nodes, formula, root) {
2492:       return Selector.pseudos.nth(nodes, "1", root, false, true);
2493:     },
2494:     'last-of-type':     function(nodes, formula, root) {
2495:       return Selector.pseudos.nth(nodes, "1", root, true, true);
2496:     },
2497:     'only-of-type':     function(nodes, formula, root) {
2498:       var p = Selector.pseudos;
2499:       return p['last-of-type'](p['first-of-type'](nodes, formula, root), formula, root);
2500:     },
2501: 

nth-child, nth-last-child, nth-of-type, nth-last-of-type, first-of-type, last-of-type, only-of-type はすべてCSS3で追加予定のものです。

nth-child(n)は指定された要素が親から見てn番目の子供である要素にマッチします(n==1が先頭,以下同様⁠⁠。

nth-last-child(n)は親から見て子供たちのうち後ろから数えてn番目の要素にマッチします。

nth-of-type(n)は親から見て,子供のうち同名要素のn番目の要素にマッチします。

nth-last-of-type(n)はその逆で,同名要素のうち後ろから数えてn番目の要素にマッチします。

first-of-type(n)はnth-of-type(1)と同じです。

last-of-type(n)はnth-last-of-type(1)と同じです。

only-of-typeは同名要素が兄弟で自分しかいないような要素にマッチします。

これらのハンドラはすべて後述するSelector.pseudos.nth()を呼び出すようになっています。

2502:     // handles the an+b logic
2503:     getIndices: function(a, b, total) {
2504:       if (a == 0) return b > 0 ? [b] : [];
2505:       return $R(1, total).inject([], function(memo, i) {
2506:         if (0 == (i - b) % a && (i - b) / a >= 0) memo.push(i);
2507:         return memo;
2508:       });
2509:     },
2510: 

次のnth()から使われるSelector.pseudos.getIndices()関数です。

先のCSS3セレクタは,an+b という式を受け付けるようになっています。例えばテーブル行に nth-child(2n+1) を適用すると奇数の行のみが対象になります。

この関数に渡されるのは an+b のa, bの部分,および対象となるノード配列の長さtotalです。返り値はnが変化した時のインデックス値の集合で,配列で返します。

aが0の場合はnがいくつでも答えはbなので,[b] のみを返し,bが0以下なら対象ノードが存在しないのでから配列を返します。

そうでなければ,$R(1, total) で対象ノード配列のインデックスすべてを含む配列をつくり,それを inject([]) に渡して返す配列を積み上げます。ループ処理時の2506行目の式は,an + b = i とすると n = (i - b) / a なので,剰余 (i - b) % a が0かつ商が0以上の時に返り値用のmemoにpush()する,となっています(したがってbを大きくしすぎて商が負になってしまうインデックスは答えに含まれません⁠⁠。

著者プロフィール

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

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