12 | 2008/01 |  02

  1. 無料サーバー

search phpbb-phpbb-FC2BLOG-Info-Edit Template-Post-Edit-Upload-LogOut

anything from here

CSSやJavascript自習の苦闘史を綴っていきたい。恐縮ですがJavascriptを有効にしてご覧ください。
2005年12月から社会問題も掲載!


jQuery()の挙動を解読する(16) Event 関連の各種クラスメソッド──jQuery解読(27)

jQuery Event 解読 contents
全てのエントリイが ver1.2.2 対応に改訂済みです。
  1. Eventオブジェクトの抽象性と特定化・個別化の必要性
  2. Event API の概要と $(args).eventtype() メソッド解読
  3. $(args).bind()、$(args).one()、$(args).toggle()、$(args).hover() 解読
  4. $.event クラスメソッドの概要と $.event.add()、$.data() 解読
  5. $.event.handle()、$.event.fix() 解読
  6. $(args).trigger()、$(args).triggerHandler()、$.event.trigger() 解読
  7. $(args).unbind()、$.event.remove()、$.removeData() 解読
  8. $(document).ready(f)、bindReady()、$.ready() 解読
  9. $.event.special[t].setup()、$.event.special[t].mouseenter()、$.event.special[t].mouseleave() 解読 (t="type")──これらのメソッドは ver1.2.2 で追加された
  10. 終わりに

$.event クラスメソッド 及び イベント処理に係る jQuery クラスメソッド等について

jQuery.js の Event 処理を理解するためには、関連するインスタンスメソッドから呼び出される各種のクラスメソッドや、その中から呼び出される関数を理解しなければなりません。というよりも、Event 関連の全てのインスタンスメソッドが直接・間接的にクラスメソッドを呼び出して定義されているので、jQuery.event クラスメソッドを理解しなければ jQuery.js のイベント処理は全く理解出来ないでしょう。

併せて、jQuery.event クラスメソッドから呼び出される jQuery クラスメソッドも理解しなければなりません。

そこで、ここでいうクラスメソッド等が何を指しているのか、まず明らかにする必要があります。

なお、「イベントハンドラー」という言葉は、イベントオブジェクトを操作するための3つのオブジェクトのセット──イベントタイプ、dataオブジェクト及びイベントハンドラー関数──を指しています。

Event 処理に不可欠な クラスメソッド等一覧
  1. $.event.add()…… eventtype()、bind()、one() 及び bindReady() の各インスタンスメソッドから直接・間接的に呼び出される。イベントハンドラーを bind (登録)するメソッドであり、jQuery.jsにおいてイベントを処理するための根幹を為すメソッド。
    なお、このメソッドは他に、clone() インスタンスメソッドからも呼び出される。
  2. $.event.remove()……unbind() メソッドから呼び出される。add()より bind されたイベントハンドラーを削除するメソッド。
  3. $.event.trigger()…… trigger() 及び triggerHandler() から呼び出されるメソッド。特定した要素ノード(特定されていない場合には全ての要素ノード)において、既に登録されているイベントハンドラーを指定して起動させる。
    なお、このメソッドは AJax 用の2つのクラスメソッド──ajax() 及び handleError()── からも呼び出される。
  4. $.event.handle()……add()メソッド及びその中の addEventListner や attachEvent() メソッドによって、DOM読み込み完了時には各種のイベントハンドラーは bind 済みとなる。 handle()メソッドは この bind 済みのイベントが発生した場合に起動されるメソッドであり、data()により定義され、管理される jQuery cache オブジェクト内から、イベント発生毎に対応するハンドラー関数を選択して、実行させるメソッドである。
    ※ 非常に分かりにくい説明であるが、簡潔に言うとこうなってしまう。
  5. $.event.fix()…… handle() クラスメソッドから呼び出される。イベントオブジェクト及びその各種プロパティをクロスブラウザ対応に正規化する。
  6. $.event.special["type"].setup()…… ver 1.2.2 で新設され、type には ready、mouseenter 及び mouseleave の3つのイベントタイプ名が入る。
    setup() はこれらのイベントを登録するメソッドである。
    例えば、$(document).bind("ready",fn) でも $(document).ready(fn) メソッドと同様の効果を得られるよう ver1.2.2 で追加されたが、この場合にこの setup() メソッドが呼び出される。
    また、mouseenter/leave イベントは、setup()メソッドによりIE 以外のブラウザの場合には mouseover/outに置換される。
  7. $.event.special["type"].teardown()…… ver 1.2.2 で新設され、 bind() で登録された ready、mouseenter/leave イベント及び mouseover/out イベントを unbind するためのメソッド。
  8. $.event.special["type"].handler()…… mouseenter/leaveイベントはIE 以外のブラウザの場合には mouseover/out イベントに置換されてバインドされるが、この時のイベントハンドラー関数で、 event オブジェクトの type プロパティ名置換(mouseover → mouseenter、mouseout → mouseleave)や、mouseenter/leaveイベントハンドラー関数の起動を行う関数。
  9. $.data()…… add()、remove() 及び handle() メソッドから呼び出される、イベント処理に欠かせないクラスメソッド。イベントハンドラーの選択・特定化の為に利用する。
  10. $.removeData()…… remove() メソッドから呼び出されるイベント処理に欠かせないクラスメソッド。$.data()と対を為すメソッドで選択・特定化されたイベントハンドラーの削除の為に利用される。
  11. $.ready()…… jQuery.js 内の数少ない名前付き関数である bindReady() 関数から呼び出される。DOM 読み込みが確かに完了したかどうか確認し、jQuery.readyListプロパティに未実行の登録関数が存在していれば、それを実行する。また memory leak を避けるための処理を行う。

以後のエントリイにおいて、以上のメソッドを全て解読する予定ですが、まずこのエントリイでは add() メソッドと、それを理解するために不可欠な $.data() クラスメソッドを解読します。

▲ToTop

$.event.add() メソッド 【ver1.2.2 で若干改訂された】 解読

タイトルで言うところの「 ver 1.2.2 における若干の改訂 」とは、 $.event.special プロパティの新設(mouseenter/leave 等への対応など)とセットになったイベントバインドに係る部分の変更を指しています。

以下で言う「中域」とは独自に作った用語で、所謂 global でもなく、かといって地域変数でもない名前空間です。それは jQuery.js コード全体を包含する無名関数内における最上位の名前空間を意味しています。他に適当な言葉が見つからないため「中域」としました。

中域変数は jQuery .js 内においてどこからでも利用できる変数であり、同時に jQuery.js 内のメソッドや関数を利用し終えた後にはメモリから消え去り、利用することが出来ない変数です。

jQuery.js 内ではこの空間を Namespaced Spaceと呼んでいます。

中域などと造語せずに、「jQuery名前空間」と呼ぶのが最適かも知れません。

さて、このメソッドは jQuery.js のイベント処理のスタートを切り、かつ根幹を為すメソッドです。このメソッドがイベントハンドラーを目的とする要素に bind(登録)するからです。

1. IEバグ対策など
1804: add: function(elem, types, handler, data) {
    //elem がテキストノードとコメントノードだった場合には何もしない。
1805:  if ( elem.nodeType == 3 || elem.nodeType == 8 )
1806:   return;
1807:
    //IE バグ対策として引数 elem を window とする。
    //なお、window だけではなく各要素の setInterval メソッドが利用出来
    //ない IE とは、どのバージョンなのかは調べ切れていない。

1808:  // For whatever reason, IE has trouble passing the window object 1809:  // around, causing it to be cloned in the process 1810:  if ( jQuery.browser.msie && elem.setInterval != undefined ) 1811:   elem = window; 1812:
2. guid 番号の設定

地域変数 handler(つまりイベントハンドラー関数)に、ユニークな番号(guid:1 から始まる整数値)を振る。この結果 guid の最終値は、当該頁においてユーザーが登録したイベントハンドラー関数の数マイナス1を表すことになる。(guid には post-increment演算子が利用されているので、値は式適用後には加算されるため)

1813: // Make sure that the function being executed has a unique ID
1814: if ( !handler.guid )
1815: handler.guid = this.guid++; // =jQuery.event.guid
1816:
3. data 引数が存在する場合の或る措置
 /* data が与えられている時には function(){fn.apply(this, arguments);} を
  * handler に代入し、handler の data プロパティに data を、guid プロパティ
  * に guid を、各々代入する。
  * ここで行っていることは、かなり回りくどい方法を取っているが、要は
  * 引数 data オブジェクトの引数 handler 関数オブジェクトへの関連づけである。
  * また handler は後述するように、指定した DOM ノードに type イベントと
  * セットになって bind 済みのイベントハンドラー関数であり、イベントハンド
  * ラーリストに登録された関数となる。
1817: // if data is passed, bind to handler
1818: if( data != undefined ) {
1819: // Create temporary function pointer to original handler
1820: var fn = handler;
1821:
1822: // Create unique handler function, wrapped around original handler
1823: handler = function() {
1824:  // Pass arguments and context to original handler
1825:  return fn.apply(this, arguments);
1826: };
1827:
1828: // Store data in unique handler
1829: handler.data = data;
1830:
1831: // Set the guid of unique handler to the same of original handler, so it can be removed
1832: handler.guid = fn.guid;
1833: }
4. 固有の要素イベント構造の設定
/* 第一引数として add() の elem を、第二引数として文字列 "events"を与えて
 * $.data() メソッドを実行する。しかし、この結果は undefined となり、論理演算
 * 子 || の働きで、続けて jQuery.data(element, "events", {}) が実行される。
 * こうして jQuery.cache[ id ][ "events" ] に {} が代入され、この空オブジェク
 * トが地域変数 events にも代入される。
 * ここに、id とは $.data() によって定義される uuid 番号であり、イベントが
 * 登録された各々の element 要素に対応する固有の整数値となる。
 * また jQuery.cache[ id ][ "events" ] は、これ以降のコードによってイベント
 * 名やイベントハンドラー関数をそのプロパティとして保有することになるオブ
 * ジェクトである。
 * いずれにせよ、この部分は $.data() クラスメソッドが分からないと全く理解出来
 * ない。そこでエントリイ後半で $.data() クラスメソッド について解読する。
 */
1835: // Init the element's event structure
1836: var events = jQuery.data(elem, "events") || jQuery.data(elem, "events", {}),

/* 次も$.data()メソッドを利用しているので、ここでは結論だけを記す。
 * jQuery.data(element, "handle", function(){・・・} により、地域変数 handle 
 * には jQuery.cache[ id ][ "handle" ] が代入される。ここの id 番号は 上の
 * events に代入された jQuery.cache[ id ][ "events" ] の id と同じ値である。
 * また、jQuery.cache[ id ][ "handle" ] には 1837-1849行までの無名関数が
 * 登録される。
 */
1837:  handle = jQuery.data(elem, "handle") || jQuery.data(elem, "handle", function(){
1838:   // returned undefined or false
1839:   var val; // 地域変数 val の初期値を undefined 又は false とする。
1840:
     // jQuery が未定義か、triggered が true ならば undefined か false
     // を返す。こうして、これらのケースの場合に新規のイベントの bind
     // (登録)が行わなわれないようにしている。
1841:   // Handle the second event of a trigger and when
1842:   // an event is called after a page has unloaded
1843:   if ( typeof jQuery == "undefined" || jQuery.event.triggered )
1844:    return val;
1845:

/* element 要素上で、jQuery.event.handle() メソッドを実行する関数を 地域変数
 * valに代入する。ここでは apply メソッドが実行される訳ではなく、定義され代
 * 入されるだけである。ここに、上の if が成立しなければ、つまり
 * typeof jQuery != "undefined" && !jQuery.event.triggered ならば
 * 1846 行で val 値に代入された関数が、1848 行により地域変数 handle に
 * return される。この val に代入された関数は、イベント発生時に、そのハンド
 * ラー関数を選択・確定し、実行するメソッド jQuery.event.handle() メソッド
 * を呼び出す働きをする。
 */
1846:   val = jQuery.event.handle.apply(arguments.callee.elem, arguments);
1847:
1848:   return val;
1849:  });

/* 次のブロックは ver 1.2.1 ではなかった。
 * elem をハンドラー関数のプロパティに代入している。
 * メモリーリークを避けるため、と記されているが、この一行が必要となる具体的
 * な現象や理由は不明にして分からない。
 */ 
1850: // Add elem as a property of the handle function
1851: // This is to prevent a memory leak with non-native
1852: // event in IE.
1853: handle.elem = elem;
1854:
5. イベントハンドラーの管理
/* ver1.2.2 から一度に複数のイベントタイプを登録出来るようになった。
 * 1856 行はその例示である。
 * これにより、同一ハンドラー関数を同一要素の複数イベントタイプに登録したい
 * 場合に、少し簡潔なコード記述が出来るようになった。
 */
1855: // Handle multiple events seperated by a space
1856: // jQuery(...).bind("mouseover mouseout", fn);

/*「Namespaced event handlers」という言葉が登場するが、これは jQuery名前空間
 * 内のイベントハンドラーとでも訳すのが最適だろう。
 * ver1.2 以降の jQuery.js においては、グローバル変数である jQuery 内に、
 * イベントを制御するために、data() メソッドを駆使して jQuery 固有の機構を設け
 * た。それは jQuery.cache オブジェクト内の様々なプロパティを通じてイベント
 * ハンドラーを管理するものである。
 * ここでは、その1つの準備としてカスタムイベントを制御するためのプロパティ
 * として handler 関数に type プロパティを定義し、その値にカスタム type 
 * 文字列を代入している。
 */
1857: jQuery.each(types.split(/\s+/), function(index, type) {
1858:  // Namespaced event handlers
    // type 文字列から" . "を分割文字として配列を作る。
1859:  var parts = type.split(".");
1860:  type = parts[0]; //地域変数 type に type 文字列を代入する
    //カスタム type 文字列を、handler 関数の type プロパティに代入する。
1861:  handler.type = parts[1];
1862:

/* 当該イベントに既に登録されたイベントハンドラー関数のリストを地域変数
 * handlers に代入する。
 * ここでも $.data() を理解しないと events[type] の具体的な意味は分からない。
 * events[type] は 1836 行の data() メソッドによって生成されるからである。
 * そして、1875-1878行が実行されない限り、(つまりイベントの登録が終わらな
 * い限り)events[type] は undefined となり、その時には空オブジェクトを代入
 * する。これにより当該要素ノードへの type 名別にイベントハンドラー関数を管
 * 理する。
 */
1863:  // Get the current list of functions bound to this event
1864:  var handlers = events[type];
1865:
1866:  // Init the event handler queue
1867:  if (!handlers) {
1868:   handlers = events[type] = {};
1869:
6. イベントハンドラーの bind (登録)

ver1.2.2 における Event 関連コードの大きな改訂の1つはこの部分である。

jQuery.event.special プロパティを新設し、中でブラウザ別に mouseover/out と mouseenter/leave を使い分けているのだが、1873 行はそのことと一体となっている。

この部分では jQuery.event.special に type プロパティが存在しないか、または elem.jQuery.event.special[type].setup() メソッドの返値が false の場合にだけ Listener()などを作動させるようにしている。

まず、special オブジェクト内に存在する type 名は ready と mouseenter/leave だけ(2126-2179)なので、その他の type 名のイベントの時には、1875 行以下が実行される。またブラウザが IE の時には setup()メソッドから false が返されて attachEvent() メソッドが実行される。

他方、IE 以外のブラウザの場合には、複雑な過程を辿る。 setup() メソッド内で、 mouseenter は mouseover に、mouseleave は mouseout に各々イベントタイプ名を変更し、かつ $.event.special.handler()メソッドをハンドラー関数として設定してから、bind() メソッドが再帰呼び出しされる。当然 bind()メソッドからは add() メソッドが再帰呼び出しされるので、二重の入れ子構造で処理を行うことになる。

この入れ子構造における処理過程は、別途 special() メソッド解読で詳細に述べることとする。

1870:   // Check for a special event handler
1871:   // Only use addEventListener/attachEvent if the special
1872:   // events handler returns false
1873:   if ( !jQuery.event.special[type] || jQuery.event.special[type].setup.call(elem) === false ) {
1874:    // Bind the global event handler to the element
1875:    if (elem.addEventListener)
1876:     elem.addEventListener(type, handle, false);
1877:    else if (elem.attachEvent)
1878:     elem.attachEvent("on" + type, handle);
1879:   }
1880:  }
1881:

/* 登録済みの handler 関数を固有番号に登録する。
 * 1864 行により handlers = events[type] だから、1883 行は
 * handlers = events[type][handler.guid] = handler ; となる。
 * 1888 行では type 名のイベントが登録されたことを global プロパティに記録
 * させている。
 */
1882:  // Add the function to the element's handler list
1883:  handlers[handler.guid] = handler;
1884:
1885:  // Keep track of which events have been used, for global triggering
1886:  jQuery.event.global[type] = true;
1887: });
1888:
/* 最後にまたしても I E対策である。
 * メモリリークを避けるために elem を空ににしている。
 */
1889: // Nullify elem to prevent memory leaks in IE
1890: elem = null;
1891: },

▲ToTop

$.data(elem,name,data) クラスメソッド

$.data(elem,name,data) の目的あるいは存在理由

jQuery.js は jQuery(args) によって、DOMエレメント集合を要素とする配列を取得します。例えば var tmp = jQuery("p") とすれば、変数 tmp には当該頁の全てのPエレメントノードをその要素とする配列が代入されます。

jQuery.js は、この取得配列から個別のエレメントノードを特定し操作するために、jQuery("p").index(num) や jQuery("p").get(num) などのインスタンスメソッドを用意しています。

また、Pエレメントノードに class 属性が設定すれば jQuery("p.className").get(num) 等により絞り込むことも出来ます。

しかし、これらの既定のメソッドでは、異なる名称のエレメントノードの、(単なる属性ではなく)特定の性質を有する要素集合内の、各要素を特定するには大変面倒な指定が必要となります。そこで考案されたメソッドが $.data() であり、$.remodeData() なのだと、推量します。

このメソッドは find() メソッド内や filter() メソッド内で、第一引数のみを取る data(elment) 形式で利用され id 番号の取得に利用されていますが、これら以外の箇所では全て event を処理するために、2番目以降の引数を与えて使用されています。ですから、このメソッドは event 処理のために導入されたと言っても過言ではないでしょう。

さて、data() メソッドは大変抽象的な内容です。そこで、具体的な内容を知るには event 処理コードにおいてそれが実際に使用されたケースや、その結果としての DOM ツリーを調べることが早道なのではないか、と考えました。実際そうしてみてやっとこのメソッドが理解できたのです。なお、data() メソッドと対を為している removeData() メソッドは 別途取り上げる予定なので、以下では data() メソッドのみを取り上げます。

ある例で event に係る data() メソッドの結果を見てみる

或る Web 頁において、複数のイベントハンドラーを jQuery.js を使って登録し、その直後の DOM ツリーを見てみました。以下の DOM ツリーは jQuery.cache オブジェクトの load イベントと mousemove イベントに係る部分を抜き出したものです。

さて cache オブジェクト内の様々なプロパティは data() メソッドにより作成されたものです。uuid 番号はイベントが登録されたエレメントノードに対応し、それぞれの uuid オブジェクトが必ず2つ──events 及び handle──のプロパティを有しています。そして events オブジェクトは必ず1つ以上のイベントタイプ名のプロパティを持ち、その中に既に bind 済みのイベントハンドラー関数が guid 番号を振られて登録されています。

つまり jQuery.data() メソッドによって作成される jQuery.cache オブジェクトは、必ず
  jQuery.cache[uuid]["events"][event.type][guid] = event handler function
のような構造となります。

$   ↓uuid   ↓guid
└ cache       object
  ├ 1       object  //$.data(elm,name) により振られた $(elm) に対応する uuid 番号1のオブジェクト
  | ├ events   object  //$.data(elm,"events") によって設定されたオブジェクト
  | | └ load  object  //イベントタイプ名のオブジェクト
  | |   ├ 1  function //引数 handler で与えられ、guid番号 1 を割り振られた bind 済みの 関数
  | |   |├ guid 1……1560 行で付与された番号
  | |   |├ data undefined……引数 data が付与された場合のdata
  | |   |└ type undefined……eventtype.custum と指定された場合の custum値
  | |   └ 7  function //引数 handler で与えられ、guid番号 7 を割り振られた bind 済みの 関数
  | |    ├ guid 7
  | |    ├ data undefined
  | |    └ type undefined
  | └ handle   function //$.data(elm,"handle") によって設定された関数オブジェクト
  ├ 2       object  //特定の Elementノードに対応する data() により振られた uuid 番号2のオブジェクト
  | ├ events   object  //data() において name値=eventsで設定されたオブジェクト
  | | └ mousemove  object  //イベントタイプ
  | |   └ 2  function //引数 handler で与えられ、guid番号 2 を割り振られた bind 済みの 関数
  | |    ├ guid 2
  | |    ├ data undefined
  | |    └ type undefined
  | └ handle   function //$.data(elm,"handle") によって設定された関数オブジェクト
  ├ 3 ・・(以下略)

▲ToTop

上のDOMツリーが作成される過程の説明
  1. ユーザーが定義した event 処理関数( jQuery(function(){})を含む )があれば、当該頁が開かれる時に jQuery.js の data()メソッドにより イベントがバインドされた各々の Elementノードに対応して uuid 番号が振られ、jQuery.cache オブジェクト内に uuid 名のオブジェクトが生成される。
  2. その uuid 番号名のオブジェクトの events プロパティに、イベントタイプ( 上の場合には load と mousemmove )プロパティが設定される。
  3. そのイベントタイププロパティに、イベントハンドラー関数が guid 番号名のプロパティで登録される。
  4. 1つの Element に複数のイベントタイプが登録されると、異なる guid 番号のイベントハンドラーが、イベントタイプのプロパティとして登録される。(上の場合には 1 と 7 )
  5. 一方、 uuid 番号が振られたオブジェクトには events プロパティの他に、handle プロパティが設定され、その値として jQuery.js で定義された関数が設定される。この handle プロパティの値である関数こそ、addEventListner()等のメソッドによってイベントハンドラーとして登録され、イベント発生時に駆動される関数となる。

▲ToTop

$.data() コードを分析する【ver1.2.2で変更なし】
   /* 中域変数 expando に当該 jQuery.js ファイルが読み込まれた時の時刻を
    * 記録し、中域変数 uuid(universal unique ID)と 同 win を初期化する。
    * これらの変数は jQuery.js 内の各種メソッドから随時呼び出されて利用さ
   * れる。このため uuid は決して重複せず、++uuidにより呼び出される度に
   * 1ずつ加算される、異なる整数値を提供する。
   */
588: var expando = "jQuery" + (new Date()).getTime(), uuid = 0, win = {};

   /* jQuery.cache を空オブジェクトとして定義する。このオブジェクトの中に
    * 各種の情報を登録していき、それによるイベントハンドラーの管理を可能
    * としている。
    */
640: cache: {},

 /* data()メソッド開始
  * elem が window の場合にはこれを変数 windowData に置き換える。
  * 次に elem の expando プロパティを 地域変数 id に代入する。
  * しかし、当該 elem に最初に data() メソッドを適用した場合には
  * このプロパティは存在しないから id 値は null 値となる。
  * 一方 elem[ expando ] が存在している場合にはその値がそのまま
  * id 値となる。
  */
642: data: function( elem, name, data ) {
643:  elem = elem == window ?
644      windowData :
645      elem;
646:
647:  var id = elem[ expando ];
648:

 /* 或る element に最初に data メソッドを適用した場合には id は null 
  * 値なので、地域変数 id と elem オブジェクトの expando プロパティに
  * uuid 番号を代入する。
  * id が存在すれば( 同じ element に対して二回以上 data メソッドを
  * 適用した場合等)何もしない。
  */
516:  // Compute a unique ID for the element
517:  if ( !id )
518:   id = elem[ expando ] = ++uuid;
519:

 // name 引数が存在し、かつ jQuery.cache オブジェクトの id 属性が存在
 // しなければ jQuery.cache オブジェクトの id 属性に空オブジェクトを登録する。
653:  // Only generate the data cache if we're
654:  // trying to access or manipulate it
655:  if ( name && !jQuery.cache[ id ] )
656:   jQuery.cache[ id ] = {};
657:

    // dataが定義されていれば
    // jQuery.cache[ id ][ name ]プロパティに data を代入する。
    // data が未定義の場合 jQuery.cache[ id ][ name ] は未定義となる。
658:  // Prevent overriding the named cache with undefined values
659:  if ( data != undefined )
660:   jQuery.cache[ id ][ name ] = data;
661:

 // name引数があれば jQuery.cache[ id ][ name ] を、なければ id 値を返す。
 // この結果 name があって data がない場合には未定義値 undefined が返される。
662:  // Return the named cache data, or the ID for the element
663:  return name ?
664:   jQuery.cache[ id ][ name ] :
665:   id;
666: },

▲ToTop

data() メソッドの要点
  1. まず、647行の id = elem[ expando ] = ++uuid; により id 値と elem[ expando ] 値に同一の ++uuid 値が代入されます。かつ、こうして一度 uuid が設定された要素に対しては、650行により異なる uuid は付与されません。
  2. 次に、656 行の jQuery.cache[ id ] = {}; により空オブジェクトである cache のid 属性値として空オブジェクトが設定されます。ここでも一旦 jQuery.cache[ id ] = {} が設定されれば、655行により、同じ name 値に対して二度と履行されません。
  3. そして引数に data が存在すれば、660 行によってそれが jQuery.cache[ id ][ name ] に代入されます。
    これにより固有の id 値と 任意の name 値によって、或る data を特定できる情報が cache オブジェクトに代入されることになります。
  4. 最後に、name があれば data が、name 値がなければ id 番号が return されます。
    これにより任意の element に対して固有の id 値を振ったり、あるいは固有の id 値と任意の name 値に対して data を設定することが出来る訳です。

jQuery()の挙動を解読する(15) $(args).bind()、$(args).one()、$(args).toggle()、$(args).hover()──jQuery解読(26)

$(args).bind() 解読【※ ver1.2.2で変更なし】

bind の第1引数の type は、ver1.2.2 から複数のイベント名の記述が可能となりました。しかし、bind() メソッドそのものに変化はありません。複数の type 名対応は add() メソッドで計られています。

bind() メソッドは $(args).eventtype(fn) メソッドから呼び出されますが、このメソッド単独でも意義のある活用が可能だと思います。殊に $(args).eventtype(fn) メソッドでは与えることの出来ない data オブジェクトを引数として与えられる点は bind() メソッドの単独利用を不可欠にしています。

さて、bind() メソッドのコードは極めて短いものですが、それは実質的な処理を jQuery.event.add()メソッドに委ねているからです。この後のエントリイで解説している add()メソッドを理解することが bind() メソッドを理解するためには不可欠となります。

ここでは、コードに沿って簡単に流れを解読することに留め、詳細は add() メソッド解読に委ねます。

 // 引数の data は JSON 形式のオブジェクトとして与えることが分かりやすいようです。
 /* イベントタイプが unload だったら one() メソッドを、そうでなかったら
  * this=$(args)の個々の要素ノードに対して、jQuery.event.add()メソッドを適用
  * する。fn || data, fn && data では、興味深い論理演算子の使い方をしている。
  * 第三引数は fn ? fn : ( data ? data : null) に等しく、
  * 第四引数は fn ? data : null に等しくなる。
  * これらの論理演算子の巧妙な使い方によって data や fn が与えられない場合に
  * エラーが生じないようにしている。
  */
2184: bind: function( type, data, fn ) {
2185:  return type == "unload" ? this.one(type, data, fn) : this.each(function(){
2186:   jQuery.event.add( this, type, fn || data, fn && data );
2187:  });
2188: },
data って何をするため?

それにしても第二引数の data は何をするために存在しているのか、最初は分かりませんでした。

本家サイトに拠れば、この data は object とされています。そして「Additional data passed to the event handler as event.data」──つまり、 event.data の値としてイベントハンドラーに付加されるものと説明されています。

しかし、本家サイトには実際に data を引数に取った例は示されていません。

そこで本家サイト内を探索し実例を見つけました。それは jQuery.js ver1.1.4 Release Note の中にありました。(この Release Note 内のjquery1.1.4.jsファイル内には沢山の実例が掲載されており、大変参考になります。)

第二引数 data を使った bind() メソッドの例───出典:jquery1.1.4.js

 // ハンドラー関数の中で event.data オブジェクトのfoo プロパティを引用
 function handler(event) {
   alert(event.data.foo);
 }
 // data として {foo: "bar"} を与えてクリックイベントとhandlerを bindする
 $("p").bind("click", {foo: "bar"}, handler)
 // 以上の結果、pタグをクリックすると、"bar" が alert される。

つまり、data 引数を使えば、何らかのイベント発生時に Javascript に行わせる行為に、任意の情報を渡すことが出来る、と言うことになります。

▲ToTop

$(args).one( type, data, fn ) 【※ ver1.2.2で変更なし】解読

このメソッドはバインドしたイベントハンドラー関数 fn を1回だけ駆動させ、当該頁がreLoad されるまでは二度と励起させない関数です。ボタンをクリックした際に Javascript に何かを行わせ、しかし二度目以降ののクリック時には何もさせない──そんな場合に重宝します。

 // 引数は bind() と同様です。
2190:one: function( type, data, fn ) {
 // this=&(args) の各々の要素ノードに対して、関数を実行させ this を返す。
2191: return this.each(function(){
 /* function(event) 関数を add() メソッドに渡す。
  * この add() メソッドによって地域変数 handler に function(event){・・・}関数が
  * 代入され、これが type イベント発生時のハンドラーとして bind(登録)される。
  * さて、この handler 関数は type イベントが発生して実行される際に2つのこ
  * とを行う。まず 2193 行により当該要素ノードから当該イベントの登録を抹消する。
  * 次に 2194 行により fn または data 関数を実行する。
  * ここに arguments は 2193 行より event となる。
  */
2192:   jQuery.event.add( this, type, function(event) {
2193:    jQuery(this).unbind(event);
2194:    return (fn || data).apply( this, arguments);
2195:   }, fn && data);
2196: });
2197:},

なお、ここでも論理演算子を巧みに使って、data や fn がユーザーから与えられない場合のエラーを回避しています。

▲ToTop

$(args).toggle(fn1,fn2) 【※ ver1.2.2で変更なし】解読

これは同一の要素ノードに対して、クリック時に交互に起動する2つのイベントハンドラー関数を登録するメソッドです。

2217:toggle: function() {
2218: // Save reference to arguments for access in closure
    // クロージャーからアクセスするために地域変数に引数(2つのイベント
    // ハンドラー関数)を代入
2219:  var a = arguments;
2220:
     // クリックイベント発生時の挙動を履行させる。
2221:  return this.click(function(e) {
 // this.lastToggle プロパティに、それが 0 ならば 1 を、さもなくば
 //(つまり 1 または未定義ならば)0 を代入する。
2222:   // Figure out which function to execute
2223:   this.lastToggle = 0 == this.lastToggle ? 1 : 0;
2224:
 /* clickイベントによるブラウザ固有の挙動を抑制する。
  * なお、click()メソッド実行過程において、$.event.add()、$.event.handle()、
  * $.event.fix() が実行済みなので、既にクロスブラウザ対応となっている。
  * このため e.preventDefault() はクロスブラウザ対応のメソッドとなっている。
  * この当たりのことは handle()メソッドの解読で詳述する。
  */
2225:   // Make sure that clicks stop
2226:   e.preventDefault();
2227:
 /* 1900行 の return とapply(this,・・・ により 1900 行の apply() メソッドが
  * クロージャーとなる。つまり、次行により this.fn1(e) 又は this.fn2(e) が
  * returnされ 更に1892行によってclick()メソッドが呼出し元である toggle() 
  * メソッドに return されるが、地域変数 a に代入された fn1,fn2 の値は
  * toggle()メソッド読み込み完了後も、次行のメソッドから参照出来る。
  */
2228:   // and execute the function
2229:   return a[this.lastToggle].apply( this, [e] ) || false;
2230:  });
2231: },

$(args).hover() 【※ ver1.2.2で変更あり】解読

このメソッドは $(args) 各ノードに対して2つのイベント( mouseenter/mouseleave )を一度に bind するものです。ver 1.2.1では同一名称のメソッドで mouseover/mouseout イベントを登録していましたが、1.2.2 では mouseenter/mouseleave に対応する内容に変更されました。

そもそも mouseenter/mouseleave は IE 固有のイベントであり、他のブラウザはサポートしていないはずです。ですからこれらのイベントに対応したということは、mouseover と mouseout だけでは IE で何らかの不都合が生じたため、敢えて mouseenter/mouseleave にも対応したのだと推測します。

コードを分析してみると、IEの場合には mouseenter/mouseleave 名のままでイベントを bind させており、他方でその他のブラウザでは、hover()メソッドは mouseover/mouseout 名のイベントを bind するようプログラムされています。つまり、hover() メソッドは IE の場合には mouseenter/mouseleave イベントを、その他の場合には mouseover/mouseout を扱うことになります。

さて、hover()メソッドは僅か3行でお仕舞いです。そして内容は極めて単純であり解読するまでもないでしょう。

具体的な hover メソッドの挙動解読は jQuery()の挙動を解読する(23) $.event.special オブジェクト ──jQuery解読(35) にて行います。

2233 hover: function(fnOver, fnOut) {
2234  return this.bind('mouseenter', fnOver).bind('mouseleave', fnOut);
2235 },

jQuery()の挙動を解読する(14) Event(2) Event API の解読──jQuery解読(25)

何はともあれ、まず jQuery.js がイベントを処理する Event API のコードを順に解読してみようと思います。

そのためにまず以下に、event 関連コードの全容を自分なりに整理して外観します。

Event API の概要

Event API として次の9つが jQuery 本家サイトで説明/例示されています(順番は一部変更しました)。

言うまでもなくこれらは全てjQueryインスタンスメソッドであり、 $(args) にマッチした DOM エレメントに対して各メソッドがイベントを様々に処理します。

凡例 : args は引数、fn は関数、fn の後の数字は複数のfn を区別するための便宜上のサフィックスです。

なお、インスタンスメソッド内ではいくつかの $.event クラスメソッドが呼び出されます。そしてこれらのクラスメソッドこそ jQuery.js のイベント処理の根幹を為すものです。これらのクラスメソッドについては別のエントリイで解読することとします。

jQuery.js Event API コードの概要

   API  ─  コードの概要(利用される Event 関連メソッド)
   ※ API の概略機能

概要を示すためなので説明に記述した Javascript コードは敢えて文法に従っていません。

  1. $(args).type(fn)─ fn ? this.bind(type,fn) : this.trigger(type)

    ※ type 名のイベントとイベントハンドラー関数 fn を $(args) に登録(bind)し、$(args)で指定した要素ノードで type イベントが発生した場合に fn を起動する。
    もし fn がなければ、既に登録済みのイベント「type」を起動する。

  2. $(args).bind(type,data,fn)─ this.one(type,fn) 又は $.event.add(this, type ,fn , data)

    ※ type 名のイベント、data、fnハンドラー関数を $(args) に登録し、かつ $(args) でtypeイベントが発生した場合に、fn 関数を起動する。ここに data は event オブジェクトの data プロパティに渡される optional オブジェクトで fn を通じて付加的情報をイベントに追加できる。

  3. $(args).one(type,data,fn)─ $.event.add(this, type, function(e){this.unbind(); this.fn}, data)

    ※ bind 同様に登録し、しかし1回だけの実行でそのイベントハンドラーを unbind する。

  4. $(args).unbind(type,fn)─ $.event.remove(this.type,fn)

    ※ type 名のイベントとハンドラー関数の登録を $(args) から解除する

  5. $(args).trigger(type,data,fn)─ $.event.trigger(type, data, this, true, fn)

    ※ $(args) に登録済みの type名 のイベントを optional data (配列)付きで 励起させる。またその際に fn 関数が記されていればこれも同時に起動させる。

  6. $(args).triggerHandler(type,data,fn)─ $.event.trigger(type, data, this[0], false, fn)

    ※ 1つの要素ノードに登録されたイベントハンドラー関数をブラウザの既定の動作を行わせずに起動させる。この際、 fn 関数が記されていればこれも同時に起動させる。

  7. $(args).toggle(fn1,fn2)─ return (fn1 || fn2).apply(this.[e])

    ※ 対象ノード $(args) に click イベントと2つのイベントハンドラー関数( fn1,fn2 )を登録し、対象ノードが click された時に、関数 fn1 と fn2 を交互に起動させる。

  8. $(args).hover(fn1,fn2)
      ─ return this.bind('mouseenter ,fn1).bind('mouseleave',fn2)

    ※ マウス Enter / Leave 時のハンドラー関数 fn1、fn2 を $(args) に登録し、
    併せてイベント発生時にそれらの関数を実行する。

  9. $(args).ready(fn1)─ bindReady();
      document.fn1(jQuery) ||
       $.readyList[].push(func(){return fn1.apply(this, [jQuery])});

    ※ DOMツリー読み込み完了時に fn1 関数を実行する。完了していなければ readrList 配列に fn1 関数を登録する。

ここに、これらのインスタンスメソッドは、おしなべてjQuery.fn.extend()メソッドで登録されてますので、全て jQuery.prototype プロパティに登録されます。その結果 jQuery.js の読み込みが完了した時点で、これらのメソッドの全ては「使用可能」となっています。

▲ToTop

$(args).eventtype() メソッド解読

このメソッドが jQuery.js のどこにあるのか、最初は戸惑いました。 jQuery.js のEvent 関連メソッドは add() から始まりますが、「捜索」してもなかなか「らしい」メソッドが登場しないのです。

そしてやっと見つかったと思っても、そのコードは大変複雑「怪奇」、難解です。何回(苦笑)眺めたか知れません。その何回も眺めた難解なコードは以下の通りです。

2344:jQuery.each( ("blur,focus,load,resize,scroll,unload,click,dblclick," +
2345: "mousedown,mouseup,mousemove,mouseover,mouseout,change,select," +
2346: "submit,keydown,keypress,keyup,error").split(","), function(i,name){
2347:
2348: // Handle event binding
2349: jQuery.fn[name] = function(f){
2350: return f ? this.bind(name, f) : this.trigger(name);
2351: };
2352:});

上の「個々のイベントタイプ名のメソッドを定義する」コードは次のような仕組みになっています。

  • 2344行の ("blur から 1982 行の split(",") 迄は、イベントタイプ文字列を配列に納めるコードです。
  • each() メソッドにより、この配列の個々の要素 name に対して function(i, name) が適用されます。(2348-2350行)
  • その関数内では、第一に jQuery.fn つまり jQuery.prototype プロパティに name 、つまり個々のイベントタイプ名がプロパティとして登録されます。例えば blur イベントタイプの場合には、jQuery.prototype.blur となります。(2349行)。
  • そして各イベントタイプ名のプロパティには、f をたった1つの引数とし、 f ? this.bind(name, f) : this.trigger(name) を返値とする無名関数がその値として付与されます。(2350行)
  • この無銘関数は引数 f(ユーザーによる関数)があれば、this つまり所与のインスタンスのメソッドとして bind (name,f)を 実行してその結果を返し、他方、f がなければ this のメソッドとして trigger(name) を実行してその結果を返します。
  • なお、eventtype() メソッドを prototype オブジェクトに登録することは重要な意味を持っています。jQuery.js の読み込み完了時点でそのメソッドが登録されるからです。

以上により、jQuery.js の読み込みが完了した時点で、jQuery(args).eventtype (fn) メソッドが定義されユーザーが自在に利用するだけの環境が用意されます。

イベント発生時の挙動を決めるコードは・・・

jQuery(args).eventtype (fn) メソッドは、目的の要素オブジェクトに目的のイベントタイプとそのイベントハンドラー関数を bind (登録) するだけではなく、登録された複数のイベントハンドラーを、それぞれのイベントの発生時に呼び出す機能を兼ねています。しかしそのコードはどこにあるのか、ということが次の課題となります。

この件は以後のエントリイで具体的に解明しますが、ここで簡単に結論を書いておきます。

  1. jQuery(args).eventtype (fn) メソッド実行により、jQuery(args).bind()メソッドが呼び出され、そこから更に jQuery.event.add() メソッドが呼び出されます。
  2. この add() メソッド内でお馴染みの addEventListner や attachEvent メソッドが実行され、それらのメソッドの引数として 地域変数 handle が渡されます。
  3. 既に 地域変数 handle には add()メソッド内で jQuery.event.handle.apply(arguments.callee.elem, arguments) 又は false が代入されているので、イベント発生時にはこれが呼び出されます。(1936-1848行)
  4. 呼び出されたこのメソッド内で fix() メソッドが呼び出されてクロスブラウザ対応が施され、かつ、既に登録済みでuuid番号と guid 番号によって特定可能なイベントハンドラー関数が呼び出され、実行されることにより目的が果たされます。

jQuery()の挙動を解読する(13) Eventオブジェクトの抽象性と特定化・個別化の必要性──jQuery解読(24)

jQuery.js のイベント処理

はじめに

jQuery.js のイベント処理コードは Javascript の素人である私には難しいものに思われます。特に jQuery.js Ver1.2 以降で追加されたユーザーによる独自固有名のイベントの登録と操作は、これまでの様々な書籍やWebサイトによる Javascript イベントの学習の次元を超えたものです。

それでも年末から年始に掛けて、561行に亘る jQuery.js のイベント処理コードに食らいつき、それを穴が空くほど見つめ続け、考え抜きました。

まずこうして得られた要点は、

  1. event オブジェクトの抽象性を再認識させられた
  2. それ故のイベントオブジェクトの特定化あるいは個別化措置の必要性について、納得した
  3. Private name for the event handlers については、未だに良く理解し得ない

    jQuery.js Ver1.2 の Release Note には次のように書かれています。
    You can now provide a private name for the event handlers that you bind,・・・.

等です。

event オブジェクトの抽象性

event はタイプ、バインド対象及びハンドラー関数の3つの要素から構成さますが、event オブジェクトは固有名を持たずたった一種類しかありません。そしてそれは HTML 内や Javascript コード内で、直接個別具体的に明示的に見ることは出来ません。event や e あるいは window.event などの抽象的表現でしか現れてきません。

それ故にイベントを理解しにくいのですし、ちょっと囓っただけでは理解出来ない、難解な対象として敬遠されてしまうのではないでしょうか?

jQuery はこの抽象性を処理するために、後述する「 特定化と個別化 」のための方法を採用したのだと思います。

event に係る要素を個別化する必要性

この event オブジェクトの抽象性故に、それぞれのイベントやバインド要素及びハンドラー関数を個別に取得し操作するための、何らかの特定化あるいは個別化が必要です。イベントのタイプ、バインド要素、ハンドラー関数のそれぞれを個別に特定し、相互の関係を有機的に掌握できなければ、当然それらを操作することが出来ないからです。

改めて整理すれば「個別化」が必要な対象は次の3つになります。

  1. event バインド対象要素の個別化
  2. event イベントタイプの個別化
  3. event ハンドラー関数の個別化

jQuery.js ではこれらの各々の個別化のために、Ver 1.2 以降、 jQuery.data() メソッドと jQuery.removeData() メソッドを用意し、かつ固有の ID を2つ用意しました。uuid と guid です。各々 universal unique ID 、global unique ID の略でしょう。(この2つのメソッドと2つの ID は、全て ver1.1.4 迄は存在しませんでした。)

▲ToTop

 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が開きます。