search phpbb-phpbb-FC2BLOG-Info-Edit Template-Post-Edit-Upload-LogOut
直前のエントリイで jQuery( args ).find( selector, context ) について概観しました。$().find() メソッドは僅か5行しか記述がありませんが、その僅かの記述の中に、 $.map()、$.find()、$().pushStack()、$.unique() 及び $.data()メソッドと、4つの jQuery クラスメソッドと1つのインスタンスメソッドを呼び出しています。ここで最重要なメソッドは jQuery.find( selector,elem ) クラスメソッドで、find の具体的な検索処理はこのクラスメソッドが全部請け負います。
そこでこのエントリイでは、この長大な jQuery.find() クラスメソッドの解読を行います。
jQuery.find() クラスメソッドは、1445-1635 行までの約 200 行もある長大なコードです。そこでまずその構造と各種サブルーチンの役割を概観しておきます。
下の表は$().find()から呼び出される各種のサブルーチンメソッドを登場順に一覧したもので、……より右の文章はそれぞれのサブルーチンメソッドの役割を簡潔に記したものです。
├init()
│ |
│ ├$(context).find(selector)
│ │ ├$.map(this,fn(elem){})……this 配列(=$(context)のインスタンス)の
│ │ │ 各要素 elem を fn 関数で指定した内容に変換する
│ │ │└$.find(selector,elem)……elem の中から selector にマッチする要素を抜き出す
│ │ │ ├$.trim(selector)……selector の最初と最後にある空白を削除
│ │ │ ├$.data(elem)……elem に 固有 id 番号(uuid) を振る
│ │ │ ├$.trim()
│ │ │ ├$.merge(a,b)……a, b2つの配列を合体する
│ │ │ ├$.isXMLDoc(elem)……elem が XML要素かどうかを判定する
│ │ │ ├$.merge()
│ │ │ ├$.classFilter(r,m)……r 配列の中から m というclass名のelemを抽出/除外する
│ │ │ ├$.filter(t,r)……r 配列の各要素に t 文字列によるフィルタを適用する
│ │ │ │ ├$.parse……各種のフィルタを検索するための正規表現文字列の配列
│ │ │ │ ├$.filter()……再帰呼び出し
│ │ │ │ ├$(elem).not(sel)……sel に該当する要素を elem 配列から除外する
│ │ │ │ ├$.classFilter()
│ │ │ │ ├$.props……各種属性値の名称統一化のためのオブジェクト
│ │ │ │ ├$.attr……各種属性値を処理する
│ │ │ │ ├$.data
│ │ │ │ ├$.expr……":"に続く文字列などによるフィルタリングを行うためのオブジェクト
│ │ │ │ └$.grep(arr,fn(){})……関数で指定した条件に合う要素をarr配列から抽出する
│ │ │ ├$.trim()
│ │ │ └$.merge()
│ │ └$(args).pushStack(elem)……新たにjQuery(elem)のインスタンスを作り、そのプロ
│ │ │パティ値に今のインスタンスを格納してから新インスタンスを$(args)関数に返す
│ │ └$.unique(arr)……arr配列の要素で重複があれば重複をなくしその結果を受け取る。
│ │ └$.data()
上に見られるように、$.find() の中では、沢山の jQuery クラスメソッド等がサブルーチンとして処理を請け負い、役割分担して、context から selector に該当するエレメント等を検索することになります。
長大なコードなので内容に応じて適宜分節しながら解読を進めます。
1445: find: function( t, context ) {
1446: // Quickly handle non-string expressions
1447: if ( typeof t != "string" ) // tが文字列でない場合には
1448: return [ t ]; // 配列[t]をリターンして処理を終える。
1449:
1450: // check to make sure context is a DOM element or a document
// contextが要素ノードでもdocumentでもない場合には空配列を返して処理を終える。
1451: if ( context && context.nodeType != 1 && context.nodeType != 9)
1452: return [ ];
1453:
1454: // Set the correct context (if none is provided)
1455: context = context || document; //context がなければ document を代入する
1456:
1457: // Initialize the search 検索処理用変数定義
1458: var ret = [context], done = [], last, nodeName;
1459:
1460: // Continue while a selector expression exists, and while 1461: // we're no longer looping upon ourselves /* 1462 行の while は 1620 行まで 159 行も続く長いフレーズである。 * この中で文字列 t を様々に分解しながら切り取り、様々なフィルターに掛けて * それにヒットするかどうかを検証し、対象を抽出する。 * そのための初期処理が 1463-1468 行である。 */ 1462: while ( t && last != t ) { 1463: var r = []; // 検索結果の要素ノードを格納する配列を準備 // while 終了準備処置 これ以降の行で t の値が変更されれば last != t となるから // while 処理が継続され、つまり以下の検索が反復される。 1464: last = t; 1465: // tの先頭と末尾にある空白文字を削除 1466: t = jQuery.trim(t); 1467: // 検索結果有無を示すフラグを「ナシ」とする 1468: var foundToken = false; 1469:
/* "> 任意の文字列"形式による検索を行う箇所である。 * つまり子要素指定が有る場合の抽出作業である。 * 但し、検索対象文字列 t の先頭文字が > の場合を探しているので、selector * 文字列において、既に別の検索が処理された後の二度目以降の検索処理を行う箇所となる。 */ 1470: // An attempt at speeding up child selectors that 1471: // point to a specific element tag // 先頭文字が > でこれに何らかの文字列が続く場合の正規表現文字列 //(1356行で定義済み)を re に代入 1472: var re = quickChild; 1473: var m = re.exec(t); // 検索対象 t に re が含まれるかチェック 1474: 1475: if ( m ) { // 検索対象 t に re が含まれれば... //m[1] は quickChild の定義から chars=何らかの文字列 を指す 1476: nodeName = m[1].toUpperCase(); 1477: /* 検索対象 ret 変数の各対象毎に巡回し、 * 更に ret[i]の第一子要素を初期値とし、その兄弟要素を巡回する。 * ノードタイプが要素エレメントで、それが何らかの要素名か、あるいは * quickChild 検索で抽出済みのノードネームに等しければ * その子要素を r 配列に格納する。 */ 1478: // Perform our own iteration and filter 1479: for ( var i = 0; ret[i]; i++ ) 1480: for ( var c = ret[i].firstChild; c; c = c.nextSibling ) 1481: if ( c.nodeType == 1 && (nodeName == "*" || c.nodeName.toUpperCase() == nodeName) ) 1482: r.push( c ); 1483: /* 取得した検索結果配列を ret に代入する。これにより検索対象配列 ret が * 検索結果配列に置換される。 * 一方、検索文字列 t から 今検索した文字列=「>何らかの文字列」を削除する。 * 直前検索文字列削除後の t に空白が含まれれば検索を続ける。 */ 1484: ret = r; 1485: t = t.replace( re, "" ); 1486: if ( t.indexOf(" ") == 0 ) continue; 1487: foundToken = true; // 検索結果有無フラグをtrue、つまり検索結果ありとする。
ケースBでは、(1)先頭に>があり、(2)その後に空白文字が 0 個以上あって、かつそれに(3)数字かアルファベットかアンダースコアが続くような検索文字列であるかどうかをチェックする。
ケースAでは、数字/アルファベット/アンダースコアではない文字、例えば:、,、#、$などが含まれていても構わなかったが、ケースBではこれらの文字は対象とされない。
/* 正規表現文字列 /^([>+~])\s*(\w*)/iは次のことを意味する。 * 先頭文字が>、+ あるいは ~ のいずれか(第一部分文字列)であって * その後に空白文字が 0 個以上あり、 * 数字/アルファベット/アンダースコアが0個以上続く文字列を * 大文字小文字の区別なく探す */ 1488: } else { 1489: re = /^([>+~])\s*(\w*)/i; 1490: //t に対する re 正規表現文字列の検索結果を m に代入し、それが空でなければ 1491: if ( (m = re.exec(t)) != null ) { 1492: r = []; //空配列 r を準備(検索結果受け取り用) 1493: 1494: var merge = {}; //空オブジェクト merge を準備(id照合用) 1495: nodeName = m[2].toUpperCase(); // \w* に該当する文字列を大文字にして 1496: m = m[1]; // m に第一部分文字列(>、+ あるいは ~ のいずれか)を代入 1497: // 検索対象ノードを巡回する。 1498: for ( var j = 0, rl = ret.length; j < rl; j++ ) { /* 第一部分文字が ~ か + ならば(つまり兄弟要素を探す場合には) * ret[j].nextSiblingを、また第一部分文字列が > の場合には * ret[j].firstChild(第一子要素)を、それぞれ n に代入する。 */ 1499: var n = m == "~" || m == "+" ? ret[j].nextSibling : ret[j].firstChild; // その n 要素の兄弟要素を順に巡回し //そのノードタイプが要素ノードならば、それに uuid 番号を振って id に代入。 1500: for ( ; n; n = n.nextSibling ) 1501: if ( n.nodeType == 1 ) { 1502: var id = jQuery.data(n); 1503: /* 第一部分文字が ~ で(つまり兄弟要素を探していて)既に id 番号が振られた * 要素がある場合(正確に言えば既に 1507行で merge[id}==true が与えられて * いる場合)には、兄弟要素を検索するループから抜け出す。 */ 1504: if ( m == "~" && merge[id] ) break; 1505: /* nodeName がないか、n の nodeName が先に求めた nodeName と等しい時に * 第一部分文字が ~ ならば、merge[id] に true を代入してから * 当該の n 要素を r 配列に格納する。 * またもし 第一部分文字が + だった場合には(直近の兄弟要素を見つけるのだから) * 1つ見つかればもう探す必要はないので、兄弟要素を探すループから抜け出す。 */ 1506: if (!nodeName || n.nodeName.toUpperCase() == nodeName ) { 1507: if ( m == "~" ) merge[id] = true; 1508: r.push( n ); 1509: } 1510: 1511: if ( m == "+" ) break; 1512: } 1513: } 1514: /* 見つかった要素が入った配列 r を、検索対象要素が代入されている配列 ret に代入 * し、検索文字列のtから re に代入された部分検索用文字列部分を消去し、かつその * 後の t 文字列の先頭と末尾にある空白文字を削除してから、t を更新する。 * この節の最後に、検索対象有無を示すフラグを true とする。 */ 1515: ret = r; 1516: 1517: // And remove the token 1518: t = jQuery.trim( t.replace( re, "" ) ); 1519: foundToken = true; 1520: } 1521: } //END OF 1488 else 1522:
以上の検索に引っかからない場合(つまり子要素を探すのではない場合)、次の検索処理が 1525 行から 1611 行までで行われる。この節での検索は selectors による複数条件指定、.className 指定、#idName 指定の場合が処理される。
1523: // See if there's still an expression, and that we haven't already
1524: // matched a token
1525: if ( t && !foundToken ) {
: ・・・・・
1611: }
まず t が存在していて、検索結果有無フラグが false の時、1526-1539行でカンマを t から削除する処理が行われる。なおここでは検索そのものは行わない。
1526: // Handle multiple expressions
1527: if ( !t.indexOf(",") ) { // t の先頭文字が "," の場合
/* そもそも、検索文字列にカンマが入るのは
* jQuery(context).find( selector1,selector2,・・・・,selectorN ) が履行されている時
* である。
* さて、ret 配列には 対象要素エレメントの集合である context が代入されている。
* もし、ret 配列(それは [context] に等しい)の最初の要素が context そのものに
* 等しいならば、元々 context に1つの要素しかなかったことになる。
* つまり1つの要素ノードを対象として複数のパターンのセレクターを指示していること
* になる。この時にはdoneに2つの空要素を代入することによって、jQuery.find() から
* の返値を「なし」としている。
*/
1528: // Clean the result set
// context が1つの要素しかない場合、retを空白配列とする
1529: if ( context == ret[0] ) ret.shift();
1530:
1531: // Merge the result sets
// 空配列を第一要素とし、それにret(これも空配列)が続く配列を done に代入する。
// つまり done == [ [], [] ] とする。
1532: done = jQuery.merge( done, ret );
1533:
1534: // Reset the context
// r と ret に [context]配列を代入
1535: r = ret = [context];
1536:
1537: // Touch up the selector string
// 検索文字列 t の先頭文字(これはカンマである)を削除し、替わりに
// 半角スペースに置き換える。これにより別の節での検索対象としている。
1538: t = " " + t.substr(1,t.length);
1539:
次の検索処理は、いくつかの前処理が行われてから行われる。
1540: } else {
1541: // Optimize for the case nodeName#idName
// 文字通り「ノードネーム#idネーム」の文字列を探す
1542: var re2 = quickID; // 1357行で定義されている正規表現文字列
1543: var m = re2.exec(t); //検索結果を m に代入
1544:
1545: // Re-organize the results, so that they're consistent
// m があればその要素を次のように並び替える。
// [ 0, "#", idName, nodeName ]
1546: if ( m ) {
1547: m = [ 0, m[2], m[3], m[1] ];
1548:
1549: } else {
1550: // Otherwise, do a traditional filter check for
1551: // ID, class, and element selectors
1552: re2 = quickClass; // 先頭文字に "." または "#"が1個以下ある場合の正規表現文字列
1553: m = re2.exec(t); // t を re2 正規表現文字列でチェックしその結果を m に代入
1554: }
1555:
1556: m[2] = m[2].replace(/\\/g, ""); //chars文字列内から \ を削除 1557: 1558: var elem = ret[ret.length-1]; //elem に検索対象要素配列の最後の要素を代入 1559:
/* 検索文字配列 m の2番目の値が "#" の場合の検索を行う。
* 変数 oid にgetElementByIdメソッドの結果を代入する。
*/
1560: // Try to do a global search by ID, where we can
1561: if ( m[1] == "#" && elem && elem.getElementById && !jQuery.isXMLDoc(elem) ) {
1562: // Optimization for HTML document case
1563: var oid = elem.getElementById(m[2]);
1564:
/* IE と Opera において発生するバグ( form要素内のname属性の値をid値と誤って
* 認識するバグ)に対する対策を施す。
* XPathにおいて「@id は id という名前のアトリビュートを選択する」ことを指すから
* 1569 行によって、elem 内から id 属性値として m[2] を持つ要素が正しく選別される。
*/
1565: // Do a quick check for the existence of the actual ID attribute
1566: // to avoid selecting by the name attribute in IE
1567: // also check to insure id is a string to avoid selecting an element with the name of 'id' inside a form
1568: if ( (jQuery.browser.msie||jQuery.browser.opera) && oid && typeof oid.id == "string" && oid.id != m[2] )
1569: oid = jQuery('[@id="'+m[2]+'"]', elem)[0];
1570:
/* oid が見つかった場合に、m[3]がない、つまりセレクタ文字列が nodeName#idName
* 型ではなかった時か、あるいは nodeName#idName 型の時には oid のノードネームが
* m[3] つまり nodeName に一致すれば [oid]を、そうでなければ空配列を、
* それぞれ ret と r に返す。ここに空配列とはこの検索ルーチンでは目的の要素が
* 見つからなかったことを意味する。
*/
1571: // Do a quick check for node name (where applicable) so
1572: // that div#foo searches will be really fast
1573: ret = r = oid && (!m[3] || jQuery.nodeName(oid, m[3])) ? [oid] : [];
// 全ての子孫要素 (all descendant elements) を対象とした巡回検索を行う。 1574: } else { 1575: // We need to find all descendant elements 1576: for ( var i = 0; ret[i]; i++ ) { /* 最初の文字が "#" で nodeName が有れば、その nodeName 名を tag に代入し、 * そうでない場合、"." があるか m[0] が空白ならば(つまり quickID にも quickClass * にもヒットしなかった場合)"*"を tag に代入し、以上のいずれでもない場合には chars * 文字列を tag に代入する。 */ 1577: // Grab the tag name being searched for 1578: var tag = m[1] == "#" && m[3] ? m[3] : m[1] != "" || m[0] == "" ? "*" : m[2]; 1579: // tag が "*" で、ノードネームが object だったら tag に "param" を代入する。 1580: // Handle IE7 being really dumb about <object>s 1581: if ( tag == "*" && ret[i].nodeName.toLowerCase() == "object" ) 1582: tag = "param"; 1583: // これまでの r 配列に対して、検索対象内のタグ名が「tag」である要素を追加する。 1584: r = jQuery.merge( r, ret[i].getElementsByTagName( tag )); 1585: } 1586:
1587: // It's faster to filter by class and be done with it
1588: if ( m[1] == "." )
// classFilterメソッドを利用して r 要素内から m[2] の条件に該当する要素を抽出する
1589: r = jQuery.classFilter( r, m[2] );
1590:
1591: // Same with ID filtering
1592: if ( m[1] == "#" ) {
1593: var tmp = [];
1594:
1595: // Try to find the element with the ID
1596: for ( var i = 0; r[i]; i++ )
// id 属性値が m[2] と等しければ
1597: if ( r[i].getAttribute("id") == m[2] ) {
1598: tmp = [ r[i] ]; //tmpにその要素ノードを要素とする配列を代入
1599: break; //1596行の for ループを中断
1600: }
1601:
1602: r = tmp; // r にヒットした要素ノードが入った配列を代入
1603: }
1604:
1605: ret = r; //検索対象ノード配列に検索結果を上書き代入 1606: } //END OF 1574行のelse 1607: // re2(quickID 又は quickClass正規表現文字列)に該当する文字列を // 検索文字列 t から削除する。 1608: t = t.replace( re2, "" ); 1609: } //END OF 1540行の else 1610: 1611: } //END OF 1526行の if 1612:
子要素でもid名でもclass名でもない検索の場合、filter クラスメソッドを活用して検索を行う。
この filter クラスメソッドもまた長いコードなので、これについては別のエントリイで詳述する。
// これまでの検索を終えてもなお、t に文字列が残っている場合
1613: // If a selector string still exists
1614: if ( t ) {
1615: // Attempt to filter it
// 検索対象要素 r に t 文字列によるフィルタを適用し結果を val 配列に代入。
1616: var val = jQuery.filter(t,r);
1617: ret = r = val.r; // 配列 valから要素ノードを抽出して ret と r に代入
1618: t = jQuery.trim(val.t); // 配列 val から検索文字列を取りだして t に代入
1619: }
1620: }
1621:
// ここまで来ても t が残っていたらエラーが起こったはずなので、空配列を ret に代入 1622: // An error occurred with the selector; 1623: // just return an empty set instead 1624: if ( t ) 1625: ret = []; 1626: // ret がありかつ context が ret の第一項値に等しければ、retを空配列にする 1627: // Remove the root context 1628: if ( ret && context == ret[0] ) 1629: ret.shift(); 1630: // done に ret を併合し、その結果を return する。 1631: // And combine the results 1632: done = jQuery.merge( done, ret ); 1633: 1634: return done; 1635: },
上述の第二のケースの場合、65行により新たなjQuery(context)インスタンスが生成され、上述の一連の流れを経てinit()メソッドが終了します。この流れではjQuery()インスタンスが二度作成されるため複雑な動きをします。
そこでインタープリターがどの様に各種メソッドを呼び出しているのかを Firebug を使って追跡・解読してみました。解読結果を以下に図示します。
なお、ここで行った追跡は、次のようなセレクタとメソッドを指定して、ローカルパソコン上のHTMLファイル上で行いました。追跡に使ったセレクタとメソッドは jQuery("p > a").size()です。

90%近いシェアを握っているインターネットエクスプローラの描画エンジンを利用したタブbrowser。沢山のタブbrowserがあるが、多機能、カスタマイズフリー、スクリプト利用等で一日の長がある。Gekkoエンジンへの対応も行われ、IEからの自立独立の方向に向かっている。2005年7月にはIE7が登場する見通しの中で、今後の発展が望まれる。
多様なCSS作成支援機能を備えた、タグ入力式 HTML&CSS作成支援エディタ。スキンデザインもすっきりしている。テキストエディター上で作成するよりも確実で安全にタグ打ちが出来る。
文字コードを選べないのが欠点。
StyleNote同様のタグ入力式 HTML&CSS 作成支援エディタ。長年使用してきたが現在StyleNoteに乗り換えつつある。
クリップボード履歴情報を活用する為のソフト。画像まで履歴を取ってくれるのが嬉しい。このソフトを使わない日は絶対ない程に重宝し、愛用している。
起動中のウィンドウの「コピーできない」説明文などの文字列を取得し、コピー可能な文字データにするツール。何かと便利。
ストリーミングデータを保存することが出来るソフト。動画利用には不可欠なソフトだ。
無料ながらレイヤー機能を有し、スクリプトによる拡張も可能な、sleipnir作者が提供している優れもの画像編集ソフト。
画面キャプチャソフトと言えばこれに勝るものなし、ではないだろうか? 様々な取得方法を有しており、ブログ作成にもHomepage作成に不可欠だ。Jtrimと並んでWoodyBellsの作品。
複数ファイルの同時編集は出来ないが、透過pngも作れる画像編集ソフト。
(以下当該サイトから抜粋)初心者にも簡単に操作が出来るフォトレタッチソフトです。多くの加工機能で画像に様々な効果を与えることができます。非常に軽快に動作するため、ストレスなく操作できます。
Animation Gifファイルを作れる無料ソフト。
キャプチャソフト。画面内にサイト全体が表示しきれない場合でも、これを使えば全体をキャプチャすることが出来る。
画像処理。画像のフォーマット変換のみならず、色数やサイズ、圧縮率の変更まで一括処理できてしまう『BatchGOO!』は、大量の画像をまとめて処理したいときに大変便利なソフト。BMP, TIFF, JPEG, PCX, PNG の相互変換をはじめ、色数・サイズ・解像度の統一、JPEG圧縮率の調節など、ホームページ用の画像や携帯電話用の壁紙を揃えるのに抜群の相性を見せる。(Vectorの当該ソフト紹介頁より抜粋引用)
名前から直ぐに想像が付くように画像のサイズを測るためのソフトだ。Homepage作成には欠かせない。2カラム、3カラムのレイアウトを行う場合に大変重宝する。
ランチャーソフトは沢山あるが、中でもこれが一押しだ。2年以上使ってきたがその操作性には毎日満足している。これを使い始めてからデスクトップには一切のアイコンを表示することをやめてしまった。
AdobeReader7によって、起動時間が長すぎるという長年のユーザーの不満はある程度解消した。そのためこの高速化ソフトは存在価値が低下してしまったかもしれない。AdobeReader6迄はこのソフトによる起動高速化で恩恵を受けてきた。
IE専用が難点だが、様々なサイト内でIDやパスワードを入力するのに重宝するソフト。コンテキストメニューから簡単に起動できるのがGood! sleipnir等のIEの描画エンジンを利用しているブラウザでも使える。
利用しているパソコンの諸元値を取得するには、このソフトがベストだ。インストール済みソフトの一覧が取得できるのも嬉しい。
WMPは機能が豊富なだけ重い。RealPlayerも同様だ。そこでMedia Player Classicを使いたい。動作が軽快なだけではなく、対応しているファイル形式もすこぶる多く、これひとつで、wmvもrmも表示できてしまうのだから凄い! 数多あるMedia Playerの王様と言えるだろう。
自宅でPCを起動しているときには必ず起動しているメディアプレーヤー。何かと過剰なWinampよりも、起動も速くスキンはシンプルだ。
DivX, Xvid, Mov, Vob, Mpeg, Mpeg4, avi, wmv, dv, などの動画をDVD-Video形式に変換できるフリーソフト。クリックするとDVD関連ソフト紹介サイト=「DVDなToolたち」なるHomepageが開きます。