アンケートご協力のお願いgihyo.jpでは,2010年度に向けて豪華プレゼントが当たる読者属性アンケートを実施しております。ご協力ください。

gihyo.jp » DEVELOPER STAGE » 特集 » prototype.jsを読み解く » 第8回 Prototypeライブラリ(2277~2620行目)

prototype.jsを読み解く

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

ユニットテスト

PrototypeライブラリをSubversionからcheckoutすると,test/ ディレクトリ以下にユニットテスト用のファイルが含まれています。

今回ライブラリ内に疑問点などがあったり,こういうCSSセレクタで正しく動くのだろうか,というのを確認するのに便利に使いました。

assertEnumEqual([], $$('#p + #p'));

というような行を追加するだけで、ブラウザから簡単にテストが行えます。

では,Selectorクラスの後半です。

2277:   handlers: {
2278:     // UTILITY FUNCTIONS
2279:     // joins two collections
2280:     concat: function(a, b) {
2281:       for (var i = 0, node; node = b[i]; i++)
2282:         a.push(node);
2283:       return a;
2284:     },
2285: 
2286:     // marks an array of nodes for counting
2287:     mark: function(nodes) {
2288:       for (var i = 0, node; node = nodes[i]; i++)
2289:         node._counted = true;
2290:       return nodes;
2291:     },
2292: 
2293:     unmark: function(nodes) {
2294:       for (var i = 0, node; node = nodes[i]; i++)
2295:         node._counted = undefined;
2296:       return nodes;
2297:     },
2298: 

2277行目からは,様々な処理を詰め込んだSelector.handlersです。まずは内部で使われるユーティリティ関数です。

concat()は,ノード配列aにbをマージするだけです。aの後ろにbが連結された配列を返します。

mark(), unmark() は,渡されたノード配列の中身各々について,_countedプロパティをtrueに設定したりundefinedにしたりします。両方とも元々の配列(ただし_countedプロパティが操作されたもの)を返します。これは,CSSルールによるフィルタを行う際に,

  1. 基準となるノード配列全体をunmark()する
  2. ルールに該当する要素を抽出する
  3. 抽出された配列をmark()する
  4. 必要に応じて2, 3を繰り返す
  5. 基準となるノード配列から_countedにマークされたものだけを抽出する

とするためなどに使われます。これで順番を保ったまま,重複なしで抽出することができます。

2299:     // mark each child node with its position (for nth calls)
2300:     // "ofType" flag indicates whether we're indexing for nth-of-type
2301:     // rather than nth-child
2302:     index: function(parentNode, reverse, ofType) {
2303:       parentNode._counted = true;
2304:       if (reverse) {
2305:         for (var nodes = parentNode.childNodes, i = nodes.length - 1, j = 1; i >= 0; i--) {
2306:           node = nodes[i];
2307:           if (node.nodeType == 1 && (!ofType || node._counted)) node.nodeIndex = j++;
2308:         }
2309:       } else {
2310:         for (var i = 0, j = 1, nodes = parentNode.childNodes; node = nodes[i]; i++)
2311:           if (node.nodeType == 1 && (!ofType || node._counted)) node.nodeIndex = j++;
2312:       }
2313:     },
2314: 

2302行目からはindex()関数です。pesudos.nth()関数からのみ呼ばれています。例えばE:nth-child(99)というセレクタが指定されている場合,pseudos.nth()のnodesには要素Eの配列が入っています。このnodesの中身それぞれの.parentNodeがこのindex()関数に渡されてきます。この時reverseが偽,ofTypeも偽だとすると,2310 行目からのループにおいて,nodes = parentNode.childNodes はnth()内のnodesの一要素である要素Eの兄弟要素全体,となります。この兄弟要素全体をループで回し,該当する要素のnodeIndexプロパティに順番となる数字を入れていきます。この時対象はnodeType == 1 (ELEMENT_NODE) で,かつofTypeが偽かnode._countedが真の場合のみになっています。

ofTypeが偽の場合は,nth-child()系の擬似クラスになるので,その場合は必ずnodeIndexに値を代入します。

ofTypeが真の場合はnth-of-type()系の擬似クラスであり,その場合はnth()のnodesは対象となる要素Eのみが含まれる配列となっているはずです。その時にnth()側でmark(nodes)を使って_countedプロパティがチェックされているはずです。これを利用して,自分自身と同じ要素名を持つ要素だけを順番に数える,という処理にしています。

2315:     // filters out duplicates and extends all nodes
2316:     unique: function(nodes) {
2317:       if (nodes.length == 0) return nodes;
2318:       var results = [], n;
2319:       for (var i = 0, l = nodes.length; i < l; i++)
2320:         if (!(n = nodes[i])._counted) {
2321:           n._counted = true;
2322:           results.push(Element.extend(n));
2323:         }
2324:       return Selector.handlers.unmark(results);
2325:     },
2326: 

2316行目からはunique()関数です。

フィルタ処理で蓄積されたノードの配列に対して,重複が無いようにした配列を返します。

これはSelector.findChildElements()か,Selector.compileMatcher()で生成されるmatcher関数の最後から呼ばれます。

_countedを使う関数は使用後にクリアすることになっているので,ここでは自分で_countedを使って初出のものだけをresults配列に積んでいき,最後にunmark()でクリアしています。

2327:     // COMBINATOR FUNCTIONS
2328:     descendant: function(nodes) {
2329:       var h = Selector.handlers;
2330:       for (var i = 0, results = [], node; node = nodes[i]; i++)
2331:         h.concat(results, node.getElementsByTagName('*'));
2332:       return results;
2333:     },
2334: 

2327行目からは"COMBINATOR FUNCTIONS"すなわちCSSセレクタの結合子に対するハンドラ関数です。

descendant()は,"要素A 要素B"という形式の,ある要素より内側にある要素を示すものです。

CSSセレクタのパーサがdescendant combinatorにマッチするタイミングは,上記"要素A 要素B"の間の空白文字の位置となります。このハンドラに到達するタイミングでは,引数のnodesには"要素A"にマッチするものが集められています。ここでdescendant()ハンドラ関数の役割は,次の"要素B"セレクタでフィルタすることができるように(handlers.tagName()で行われます),nodesの内側の要素を列挙することです。

そのための実装は,渡されたノード配列に対して,各々の子供要素をgetElementsByTagName('*')ですべて列挙して,返り値用のresultsに蓄積して返す形となっています。

2335:     child: function(nodes) {
2336:       var h = Selector.handlers;
2337:       for (var i = 0, results = [], node; node = nodes[i]; i++) {
2338:         for (var j = 0, children = [], child; child = node.childNodes[j]; j++)
2339:           if (child.nodeType == 1 && child.tagName != '!') results.push(child);
2340:       }
2341:       return results;
2342:     },
2343: 

2335行目からはchild()ハンドラです。

セレクタの表記では"要素A > 要素B"となり,直下の要素を示します。

渡された各ノードに対してchildNodesプロパティの配列をループで取得し,nodeTypeがELEMENT_NODE(1)のものだけを抽出して返しています。

IEではコメントノードのnodeTypeが1 (ELEMENT_NODE)となってしまう場合があるようで,この場合はtagNameが'!'となるのでこれもスキップします。

2338行目のchildrenは定義はされていますが使われていないようです。

2344:     adjacent: function(nodes) {
2345:       for (var i = 0, results = [], node; node = nodes[i]; i++) {
2346:         var next = this.nextElementSibling(node);
2347:         if (next) results.push(next);
2348:       }
2349:       return results;
2350:     },
2351: 

2344行目からはadjacent()です。

CSSセレクタでは"要素A + 要素B"とする部分です。隣接するふたつのノードが対象ですので,渡されたノード配列に対してfor文ですべてループし,各々に対してSelector.handlers.nextElementSibling()を呼び出して,要素が返ってくればpush()しています。

これでresultsで返すのは"要素B"にあたるものの集合,となります。

2352:     laterSibling: function(nodes) {
2353:       var h = Selector.handlers;
2354:       for (var i = 0, results = [], node; node = nodes[i]; i++)
2355:         h.concat(results, Element.nextSiblings(node));
2356:       return results;
2357:     },
2358: 

2352行目からはlaterSibling()です。

原典がどこかわかりませんが,"要素A ~ 要素B"という書式で,"要素A"の兄弟要素のうち,順番が"要素A"より後ろとなる兄弟にマッチします。

実装は,渡された各ノードに対して,Element.nextSiblings()を呼び出して,候補となる要素を配列で返してresultsにマージする,という形になっています。

2359:     nextElementSibling: function(node) {
2360:       while (node = node.nextSibling)
2361:               if (node.nodeType == 1) return node;
2362:       return null;
2363:     },
2364: 

2359行目からはnextElementSibling()です。

これは主に他のハンドラ関数から呼ばれるヘルパ関数で,自分の後続兄弟のうち,ELEMENT_NODE(1)のものが見つかったらそれを返す,というものです。

2365:     previousElementSibling: function(node) {
2366:       while (node = node.previousSibling)
2367:         if (node.nodeType == 1) return node;
2368:       return null;
2369:     },
2370: 

previousElementSibling()は nextElementSibling()の逆で,自分の前にある兄弟の内で,最初に見つかった ELEMENT_NODE(1) であるものを返します。

著者プロフィール

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

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

コメント

コメントの記入

パスサポ

多数の情報処理技術者試験対策書籍の発行実績を誇る技術評論社がお届けする,資格試験合格サイト「めざせ! 情報処理試験 パスサポ」が開設されました。

ピックアップ

サクセスストーリーに続く,快適サーバー運用管理のヒント!

データの増大,煩雑な管理,システムダウン,セキュリティなど,迫りくる課題からシステム管理者の負担を軽くするポイントを解説します。

gihyo.jp インフラエンジニア情報局

ネットワークやITにかかわるあらゆる業種で必要とされるインフラエンジニアに向けた技術情報や心構え,その魅力について多角的に紹介。

テストエンジニア ステーション

いま,ITに関わるあらゆる開発業務で注目されつつあるテスト系エンジニアをターゲットにしたコンテンツサイトを展開します。

一行クイックアンケート

gihyo.jpで取り上げてほしいネタは?

※検索はページ右上の検索ボックスをご利用ください。

その他の連載

読むウェブ ~本とインタラクション

ディスプレイで読む活字とそのインタラクション(interaction:相互作用)について,最新Webを紹介しながら読み解いていく。

いま,見ておきたいウェブサイト

この連載では,国内外の最新のウェブサイトを隔週更新で取り上げ,これら最新サイトの特徴や素晴らしい部分を,さまざまな角度から解説していきます。

Windows phoneアプリケーション開発入門

Windows Marcketplace for Mobileがサービス開始され,作成したアプリケーションを個人でも世界をターゲットに公開できる環境が整ってきました。これを機にWindows phoneアプリケーションの開発をしてみませんか?

ここは知っておくべき!Windows Server 2008技術TIPS

5年ぶりのサーバOSとなったWindows Server 2008が出荷されて早2年。2009年にはR2が出荷され,再び注目を集めています。発売前から実施したトレーニングによって感じた,インフラエンジニアの方々に知っておいていただきたい機能を中心にご紹介します。

キーパーソンが見るWeb業界

本連載はWeb Site Expert/gihyo.jpとの連動企画です。阿部淳也, 長谷川敦士, 森田雄のお三方による,Web業界をテーマにした座談会です。

きたみりゅうじの聞かせて珍プレー

ソフトウェア開発の現場で体験したトホホな失敗,思わずうなる珍プレーをきたみりゅうじ氏が四コママンガで紹介。みなさんからの投稿もお待ちしてます!

ActionScript 3.0で始めるオブジェクト指向スクリプティング

野中文雄氏が,簡単なスクリプトは書いたことがあるという初級者を対象に,ActionScript 3.0の基本からクラス定義までを解説します。

まだ間に合う「ITパスポート」受験対策 原山先生の短期合格塾

この連載では,4月18日のITパスポート試験の受験に向けて,短い期間で効率良く受験対策を行う方法や,確実に得点するための裏ワザなどを伝授していきます。

連載一覧

gihyo.jp

  • DEVELOPER STAGE
  • ADMINISTRATOR STAGE
  • WEB+DESIGN STAGE
  • LIFESTYLE STAGE
  • SCIENCE STAGE
  • NEWS & REPORT

書籍案内

  • 新刊書籍
  • 書籍ジャンル一覧
  • 書籍シリーズ一覧
  • 新刊ピックアップ
  • ロングセラー
  • 電脳会議

定期刊行物一覧

  • Software Design
  • WEB+DB PRESS
  • Web Site Expert
  • 組込みプレス