12 | 2008/01 |  02

  1. 無料サーバー

User forum-FC2BLOG-Info-Edit Template-Post-Edit-Upload-LogOut

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


jQuery()の挙動を解読する(24) jquery.js 1.3.2 における jQuery 定義コードを分析する──jQuery解読(36)

ver1.3.2 における jQuery インスタンス生成コード

jQuery オブジェクトの定義コードは、1.2 → 1.2.1 → 1.2.2 と 1.2x で頻繁に改訂されました。2008年1月から2月に掛けて根幹部分の定義が立て続けに更新されたのです。しかしその後は 1.3.2 まで1.2.2の定義が継承されており、定着した感があります。(以下行数は ver 1.3.2 による)

ver 1.2.2 の変更によって(25行のコメントにもあるように)、jQuery 関数オブジェクトは そのプロトタイププロパティである init() メソッドをコンストラクタとするインスタンスを受け取るものと、定義されました。

 24: jQuery = window.jQuery = function( selector, context ) {
 25:  // The jQuery object is actually just the init constructor 'enhanced'
 26:  return new jQuery.prototype.init( selector, context ) :
 27: },

ここで興味深いことは、ver1.2.1までと異なって、jQuery 関数オブジュクトそれ自体がコンストラクタなのではない、ということです。コンストラクタは jQuery のプロトタイプオブジェクトの init メソッドになっています。

さて、最初はこの変更されたコードの意味及び変更の意義が分かりませんでした。そこで改めて new 演算子やコンストラクタについて学び直さなければなりませんでした。分かっていたつもりなのに、結局半可通だったことを再認識させられたのです。

通常の説明におけるコンストラクタ、インスタンス、new 演算子(復習)

通常コンストラクタ、インスタンスの学習コード等では、例えば次のような例示が行われます。

 var Rectangle = function (x,y){ this.x=x; this.y=y; }
 Rectangle.prototype.area= function(){ return this.x * this.y };
 Rectangle.prototype.outer = function (){ return 2*(this.x + this.y) };
 var inst = new Rectangle(2,3);
 inst.area();  // result = 6
 inst.outer(); // result = 10

この例では、Rectangle コンストラクタ関数から生成されたインスタンスオブジェクト( inst )は、そのコンストラクタオブジェクト ( Rectangle ) に関連づけられたプロトタイプオブジェクト ( Rectangle.prototype ) からプロパティを継承する、と説明されます。

つまり、プロトタイプオブジェクトのプロパティである area() メソッドや outer() メソッドが inst オブジェクトに継承されるので、上記例の最後の2行のような結果が取得出来る、という説明です。

次に、Javascript第5版によれば「 new 演算子は、(1)空のオブジェクトを生成し、(2)そのオブジェクトへの参照をキーワード this に代入し、(3)コンストラクタ関数の prototype プロパティをそのオブジェクトの prototype として設定し、(4)そのオブジェクトのメソッドとしてコンストラクタ関数を呼び出す」と説明されています。

上記の Rectangle に沿って言えば、(1)空オブジェクト(仮に obj とする)を生成し、(3) this の参照先を obj とし( this = obj )、(2) this.prototype を設定し ( この prototype 参照先は Rectangle.prototype に同じなので this.area()、this.outer() が設定され )、最後に (4) Rectangle.apply( this, [x,y] ) を実行することなります。

注目すべきことは、コンストラクタ関数の実行は最後に行われるのであって、その前にプロトタイププロパティが new 演算子によって新たに生成されたオブジェクトに設定されることです。もう一度順番を書けば
  (1) obj 生成
  (2) this = obj 設定
  (3) this.prototype = Rectangle.prototype 設定
  (4) Rectangle.apply ( this, [x,y] ) 実行
となります。

jQuery 定義コードにおけるコンストラクタ、インスタンス、new 演算子

次に同じ事を jQuery 定義コードに適用してみます。19行の new 演算子とそれに続く init 関数は
 (1) obj 生成
 (2) this = obj 設定
 (3) this.prototype = jQuery.prototype.init.prototype 設定
 (4) jQuery.prototype.init.apply(this, [selector, context]) 実行
を行うことになります。

さて、ここに登場する jQuery.prototype.init.prototype の値は何なのでしょうか? 一体、initメソッドのプロトタイププロパティとはどんな内容なのでしょうか? それはどこで定義されているのでしょうか?───こんな疑問が湧いてきます。

そしてその答えを探したところ、それは ver 1.2.2 以降で新設された次の2行にありました。

540: // Give the init function the jQuery prototype for later instantiation
541: jQuery.prototype.init.prototype = jQuery.prototype;

つまり、jQuery.prototype.init メソッドのプロトタイプは jQuery 関数オブジェクトのプロトタイプを参照し、それに等しくなります。

こうして、jQuery 関数オブジェクトはコンストラクタ init 関数から return される実行結果 ( それは this であったり、様々な指定によって生成された[obj]であったり、[selector]であったりする ) を受け取ると共に、jQuery.prototype プロパティに定義された様々なメソッドやプロパティを継承することになります。

init は従来通り初期化関数として作動し、かつ jQuery.prototype に設定された様々なメソッドやプロパティは jQuery 関数オブジェクトに継承されるわけです。

それにしても何故、jQuery.prototype オブジェクトのメソッドである init() をコンストラクタとするのでしょうか?

1.2.1までの方法を敢えて変更した理由はどこにあるのでしょうか?「インスタンスを生成してから init() メソッドを起動し、その return を jQuery オブジェクトが受け取る」という従来の方法を変えた理由が分かりません。極めて合理的なコードだと思っていたのですが、1.2.2 のコードの方が何らかのメリットがなければそうしないでしょうから、是非とも変えた理由を探りたいのですが、力及ばず、残念ながら解明出来ていません。

そこで改めて コード「 jQuery.prototype.init.prototype = jQuery.prototype 」を考えてみる

この式は「循環参照」問題を引き起こすのではないか、と言うことに気がつきました。

右辺の prototype オブジェクト内には、当然 init プロパティがあり、その init プロパティは prototype プロパティがあります。つまり jQuery.prototype.init.prototype が右辺の prototype オブジェクト内に存在しています。

以下その繰り返しになります。

その結果無限にこれが繰り返され、メモリが消費されるのではないかと考えられます。

実際、或るサイトでそのことを確認したところ下図のように際限なくinit.prototype 内にinit.prototype が内包されていることが確認できました。

どこまで行っても際限なく繰り返される init.prototype 循環にコードとして問題はないのだろうか?

jQuery()の挙動を解読する(23) $.event.special オブジェクト ──jQuery解読(35)

jQuery.event.special オブジェクトの働き

special オブジェクトは bind( elem ,fn ) 又は unbind( elem ,fn ) メソッドを使って、reaady、maouenter 及び mouseleave イベントタイプとそのハンドラー関数を elem に登録し、あるいはこれを解除する為に ver1.2.2 で新設されました。それは、(un)bind('ready',fn)、(un)bind('mouseenter', fn) 又は (un)bind('mouseleave', fn) の6つのインスタンスメソッドが起動された場合にだけ、jQuery.event.add() 又は jQuery.event.remove() メソッド内で呼び出されます。

special オブジェクトは ready、mouseenter 及び mouseleave の3つのプロパティを持っています。ready は setup と teardown の2つの関数オブジェクトで構成され、他の2つのプロパティは setup、teardown 及び handler の3つの関数オブジェクトから構成されています。(下図参照)

 special = {
  ready      : { setup : fn11, teardown : fn12 },
  mouseenter : { setup : fn21, teardown ; fn22, handler : fn23 },
  mouseenter : { setup : fn31, teardown ; fn32, handler : fn33 }
 }

ここに、それぞれの関数の役割は setup はイベントの登録用、teardown は登録済みイベント解除用、handler は各タイプのイベントに対するハンドラー関数です。

hover()メソッドと mouseover/out イベント、そしてIEへの「配慮」?

hover()メソッドは jQuery ver1.2.1 では mouseover/out イベントを扱うためのトグルメソッドでした。ところが、1.2.2 ではコードを一見すると hover()メソッドは、mouseenter/leave を扱うように変更されたように見えます。mouseover/out は hover()メソッドから切り離されてしまったような印象を受けます。

しかし、コードを詳細に見てみるとそうではないことが直ぐに分かります。hover()メソッドは IE においては mouseenter/leave イベントをサポートし、その他のブラウザにおいては mouseover/out イベントを扱うように拡張されたのです。

この拡張が何故もたらされたか、その理由は判然としませんが、過去の hover() にバグはないようですから、市場占有率が相変わらず高い IE に配慮したと理解すればよいのかも知れません。mouseenter/leave イベントが IE だけのイベントであって他のブラウザでは存在しないのに、敢えてこれらのイベントをサポートする理由は、まだ IE を無視する訳にはいかないためとしか考えられません。

関連して、それでは $(args).eventtype() 形式のイベント発動メソッドにおいて、eventtype として利用できるイベントの種類はどう変わったのか確認してみました。すると、こちらは 1.2.1 と 1.2.2 で全く同一でした。つまり、mouseenter/leave イベントは hover() メソッドだけで利用可能なのに対して、mouseover/out イベントは hover() メソッドでの利用だけではなく、それぞれを単独で利用することも想定した仕様となっています。

勿論、bind メソッドを使って mouseenter/leave イベントを単独使用することは可能でしょうが、IE だけでしか動かないコードを作る意味はありません。

▲ToTop

jQuery.event.special["type"]["setup/teardown/handler"]()メソッドの解読

specialオブジェクトは、jQuery.evemt.add() メソッド又は jQuery.evemt.remove() メソッドから呼ばれます。前者は当該イベントをバインドする際に、後者はそのイベントの登録を解除する時に、です。

type == ready の時
2126: special: {
2127:  ready: {
2128:   setup: function() {
2129:    // Make sure the ready event is setup
2130:    bindReady();
2131:    return;
2132:   },
2133:
2134:   teardown: function() { return; }
2135:  },
2136:
  • jQuery.event.special.ready.setup()
    1. $(document).bind("ready",fn) メソッドが起動されると、add()メソッドを介して special.ready.setup() メソッドが呼び出されます。この special.ready.setup() は bindReady() メソッドを呼び出して、 jQuery.ready() メソッドを DOM 読み込み完了イベントハンドラーとして登録し、DOM 読み込み完了後にこれを起動させます。
      しかし、これらの動きのどこにも第2引数の fn 関数が登場しません。正確に言えば jQuery.ready() メソッドの最後の行に至って初めて fn が起動されます。jQuery(document).triggerHandler("ready"); です。これにより fn 関数が起動されます。
    2. 次に、special.ready()メソッドの最後(2131行)には返値なしの return がありますから、未定義値が呼出し元に返されます。この結果 1875-1878 行の Listner 等のバインダー関数は起動されません。
  • jQuery.event.special.ready.teardown()

    これは単に未定義値を return するだけです。何もしません。呼出し元に戻った際には return により返された undefined は !== false ですから、1875-1878 行の Listner 等のバインダー関数は起動されません。

▲ToTop

type == mouseenter の時
2137:  mouseenter: {
2138:   setup: function() {
2139:    if ( jQuery.browser.msie ) return false;
2140:    jQuery(this).bind("mouseover", jQuery.event.special.mouseenter.handler);
2141:    return true;
2142:   },
2143:
2144:   teardown: function() {
2145:    if ( jQuery.browser.msie ) return false;
2146:    jQuery(this).unbind("mouseover", jQuery.event.special.mouseenter.handler);
2147:    return true;
2148:   },
2149:
2150:   handler: function(event) {
2151:    // If we actually just moused on to a sub-element, ignore it
2152:    if ( withinElement(event, this) ) return true;
2153:    // Execute the right handlers by setting the event type to mouseenter
2154:    arguments[0].type = "mouseenter";
2155:    return jQuery.event.handle.apply(this, arguments);
2156:   }
2157:  },
2158:
  • jQuery.event.special.mouseenter.setup()
    1. IE の場合、何もせずに呼出し元に false を return します。この結果 attachEvent() メソッド( 1878 行 )が実行されます。
    2. IE 以外のブラウザの場合、イベントタイプ名を mouseover に変え、ハンドラー関数にjQuery.event.special.mouseenter.handler を指定して bind()メソッドを再帰呼び出しします。
      再帰呼び出しされた bind() メソッドによって、更に add() メソッドも再帰呼び出しされ、その中で mouseover名の jQuery.event.special.mouseenter.handler を関数とするイベントがバインドされます。そして実は、この moueenter.handler 関数には、mouseenterイベント名と連動している fn 関数が入るようになっています。こうして、mouseenter 名で登録された fn ハンドラー関数が、mouseover 名のハンドラー関数として機能することになります。
      それが終わると再帰先から呼出し元の 2140 行に戻りますが、2141 行で true を return させることにより、呼出し元で再び addEventListner ()メソッドが実行させることはありません。
  • jQuery.event.special.mouseenter.teardown()
    1. IE の場合、何もせずに呼出し元に false を return します。この結果 detachEvent() メソッド( 1942 行 )が実行されます。
    2. IE 以外のブラウザの場合、イベントタイプ名を mouseover に変え、ハンドラー関数にjQuery.event.special.mouseenter.handler を指定して unbind()メソッドを再帰呼び出しします。
      再帰呼び出しされた unbind() メソッドによって、更に remove() メソッドも再帰呼び出しされ、その中で mouseover名の jQuery.event.special.mouseenter.handler を関数とするイベントがアンバインド( removeEventistner() メソッド起動 )されます。
      その後再帰先から呼出し元の 2146 行に戻った後には 2147行で true を return させることにより、呼出し元で再び removeEventistner() メソッドを実行させることはありません。
  • jQuery.event.special.mouseenter.handler()

    ※ この関数は IE からは呼び出されません。

    1. 内包されている要素にマウスが移動した時に、mouseout したとブラウザに判断させないために、withinElement 関数を起動させて、要素の「先祖/子孫」関係をチェックさせます。内包されている要素内にマウスがある場合には新たにイベントを発生させません。
    2. event.typeプロパティ値を mouseenter に戻した上で、mouseenter 名で登録されているイベントハンドラー関数がイベントハンドラー関数となるようにセットします。

上の説明ではもちろんのこと、special["type"].setup()メソッドは大変分かりにくいので、IE 以外のブラウザで mouseenter イベントをバインドする時の、スクリプトの進行過程を辿るフローチャート的資料を作ってみましたので、以下に掲載します。

$(elem).bind('mouseenter',fn)  process Analyze

左図をクリックすると原寸画像が別窓で開きます。mouseenter イベントタイプ名から、どのようにして mouseover イベントの登録に至るのか、その経過を分かりやすく説明したつもりです。

▲ToTop

type == mouseleave の時
2159:  mouseleave: {
2160:   setup: function() {
2161:    if ( jQuery.browser.msie ) return false;
2162:    jQuery(this).bind("mouseout", jQuery.event.special.mouseleave.handler);
2163:    return true;
2164:   },
2165:
2166:   teardown: function() {
2167:    if ( jQuery.browser.msie ) return false;
2168:    jQuery(this).unbind("mouseout", jQuery.event.special.mouseleave.handler);
2169:    return true;
2170:   },
2171:
2172:   handler: function(event) {
2173:    // If we actually just moused on to a sub-element, ignore it
2174:    if ( withinElement(event, this) ) return true;
2175:    // Execute the right handlers by setting the event type to mouseleave
2176:    arguments[0].type = "mouseleave";
2177:    return jQuery.event.handle.apply(this, arguments);
2178:   }
2179:  }
2180: }

mouseleave が mouseout に、jQuery.event.special.mouseenter.handler が jQuery.event.special.mouseleave.handler に変わる以外は、上の mouseenter の場合と全く同様ですので記述を省略します。

jQuery.event.special ["type"] の難解さについて

上に見たように、special オブジェクトはまさに「 特別に 」難解です。

特に type 名が mouseenter/leaveの場合の setup/teardown メソッドは、bind メソッドと add メソッドの再帰呼び出しを含み、しかも途中でイベント名を変更しつつ、ブラウザ毎に不必要なイベントは登録しないという「 裏技的 」技法を用いて mouseover/out イベントにも同時対応させており、難解を極めます。

このエントリイ作成に当たり、沢山の時間を要してコード進行を図示したり、何度も FireBug で追跡したり、色々チャレンジしてやっと「 理解できた 」のですが、この一連の Event 関連コード解読作業において、この箇所が最も苦労させられました。

上の記述でこれらの挙動を的確に表現できた自信はありませんし、読むだけで分かる表現になっているとも思えません。(書いておきながら、失礼なことですが...)

本来、もっと分かりやすい説明・解読文章を書ければ、と悔やまれます。

'eType'は 'mouseenter' 又は 'mouseleave' を表すものとします。 'fType'は 'mouseover' 又は 'mouseout' を表すものとします。 gn は $.event.special['eType'].handler メソッドを表します。  $(elem).bind('eType',fn)   → add( elem, 'eType', fn )     → $.event.special['eType'].setup.call(elem)       → $(elem).bind( 'fType', gn)         → add( elem, 'fType', gn ) jQuery.event.handle には special オブジェクトが呼び出されるより前の段階で、fn が登録されている。 そして gn では、add() メソッドによって既に登録されたこの fn を呼び出している。 例えば、既に jsファイルの読み込みが終わり、&(fn(){})が実行されてイベントのバインドを行う場合の、mouseenterイベントのバインド過程を綴ってみます。 >>> console.log(jQuery.cache[2]["events"]["mouseenter"][3].toString()) function () { $(this).css("background-color", "white"); } >>> console.log(jQuery.cache[2]["events"]["mouseover"][4].toString()) function (event) { if (withinElement(event, this)) { return true; } arguments[0].type = "mouseenter"; return jQuery.event.handle.apply(this, arguments); }

jQuery()の挙動を解読する(22) Event 関連エントリイを全て1.2.2対応に改訂 ──jQuery解読(34)

jQuery.jsにおけるイベント関連メソッド解読をバージョンアップ

jQuery.js の 1.2.2 登場を踏まえて、急遽イベント関連エントリイの全てを 1.2.2 対応に改訂しました。

Ver1.2.2によって、イベント関連コードにおいては、複数イベントタイプの一括登録、bindReady() メソッドの大幅改訂、IE 固有の mouseenter/leave イベントへの対応、テキストノードとコメントノードが引数になった場合のエラー防止コードの挿入、extra 関数に係る微修正、ページ座標に関する微修正、随所における IE メモリリーク対応の追加、等々の改訂が行われました。

これらの改訂の全てについて論じることは出来ませんでしたが、改訂エントリイにおいてその大半に触れることが出来ました。

よろしければ上の目次を参考に、任意のコンテンツをご覧いただければ幸いです。

jQuery()活用(3) Slide Toggle Button コードを改訂──jQuery解読(33)

全面改訂した訳は...

slideToggleメソッドの活用について、以前に詳細に記しましたが、コードの全面的な改定を行いましたので、改めてそのエントリイを改訂し、全面改定したコードについて纏め直しました。

よろしければ見てやってください。→ jQuery()活用(1) Slide Toggle Button──jQuery解読(15)

改訂したのはこれまでのコードにバグがあったためではなく、使い勝手が悪かったからです。特殊な状況でしか使えないコードだったので汎用化を図りました。具体的な改訂内容は以下の通りです。

  1. 同一エントリイ内に複数の slideToggle イベント起動ボタンと slideToggle 対象がある場合に対応させた
  2. slideToggle イベントを起動するボタンタグと、 slideToggle 表示/非表示対象要素との間や前後に、別の無関係なタグがあっても支障なく動作するようにした
  3. ボタンクリック時に背景色を変更/初期化するように設定した

ボタンクリックによる slideToggle は、利活用が容易でなければ多用出来ません。ですから汎用的であるほどに意味があると思います。

今回の改訂がその目的・機能を果たしきれているかどうか、自信はありませんが、一応の目的を果たせたと思っています。

jQuery Ver1.2.2公開───2008/1/14

1.2.2が公開された。いや、公開されていた。

数日前に未公開の(正確に言えばダウンロードページには公開されていない)1.2.2bに触れたが、この時に気がつくべきだった。

b があれば当然その前に a があるはずであり、更にその前には、添え字のない1.2.2があるはずだ───と。

変更点について

さて、最も大きな変更はグローバル変数 jQuery の定義が変わったことだろう。

従来の再帰呼び出し的定義は廃止され、「純粋に」コンストラクタとして位置づけられたのだ。

次に、本家の note頁 で強調されている jQuery(DOMElement) 呼び出しの Speedup は、単項引数でそれがDOM Elementであった場合のコードが追加され、この結果としてもたらされたものである。

なお、jQueryのコンストラクタへの純化による全体への影響がどうなるのか、近いうちに検証してみたい。

また、直近で解読してきた event 関連の若干の追加修正もあったし、細かなバグ修正も120箇所以上に及ぶようだ。

現在進めている解読作業の対象バージョンを、随時過去に遡って 1.2.2 に変更していこう、と思っている。

Event関連コードの解読において、ver1.2.2対応の説明へと順次改訂中

既に内容的に簡単なエントリイの改訂作業は終えた。

可及的速やかにイベント関連コード解読の全体を1.2.2対応へとバージョンアップしようと思う。

テスト

次のリンクをお気に入りに登録すれば使えます。
set jQuery & FirebugLite Tag

jQuery()の挙動を解読する(21) $(document).ready(f)、bindReady()、$.ready() 解読 ──jQuery解読(32)

$(document).ready(fn) - bindReady() - $.ready()

$(document).ready(fn) が bindReady() を、そしてそこから $.ready() が呼び出される三層構造で、これらのメソッドが機能します。

それぞれの役割は、順に以下のようになります。

  1. $(document).ready(fn)……(1/2) DOM読み込み完了通知イベントの登録( bindReady()メソッドの実行 )
  2. $(document).ready(fn)……(2/2) DOM 読み込み完了時の fn の実行と DOM 読み込み完了時に実行させる関数の登録、
  3. bindReady()……(2) DOM 読み込み完了のチェックと完了時の jQuery.ready()呼び出し
  4. jQuery.ready()……(3) DOM 読み込み完了を記録するプロパティの設定、未実行関数の実行、jQuery(document).bind("ready",fn)で登録された関数 fn の実行

以下に順にこれらのメソッドや関数の挙動を解読します。

$(document).ready(fn) メソッド
  1. まず、当該ページが開かれつつある段階で bindReady() 関数を呼び出します。
    この関数がブラウザ毎に異なる方法によって DOM ツリーが読み込まれたかどうかをチェックします。
    このbindReady() 関数の呼び出しが終わった後には、$(document).ready(fn) は 次の処理に進みます。
  2. 第二に、その時点で jQuery.isReady = true となっているならば(つまり jQuery.ready() メソッドが実行済みならば)、直ぐに fn 関数を実行します。
    なお、bindReady()メソッドは当該ページが unload されるまで、二度と機能することはありません。冒頭の変数 readtyBound によって一度切りの実行しか許されないからです。そこから呼び出される jQuery.ready() メソッドも一度きりの実行です。
    これらの2つのメソッドは当該頁のload完了前に一度だけ機能し、その後は unload され reload されるまでは、働くことはありません。
    つまり、当該ページに設置された2つ目以降の jQuery(document).ready(fn) 実行時には、 jQuery(document).ready(fn) が呼び出す bindReady() メソッドは直ぐに returnを返し、既に実行済みの jQuery.ready() クラスメソッドによって、isReady プロパティが true になっているので、瞬時に所定の関数 fn が実行されることになります。
  3. 第三に、その時点で DOM 読み込みが完了していなければ(※)、その return 値を受け取れる形式にして fn 関数を実行待ちリストに登録します。

    ※ この時点で DOM 読み込みが完了していない状態とは、bindReady()メソッドが起動されて DOMContentLoaded イベントの発生(またはそれに準じる状態)を待機する状態になり、その後 jQuery.ready() メソッドも起動し終えたものの、まだ DOM 読み込み完了が終わらない期間に発生します。
    DOM ツリーが巨大なほどこの待機時間は長くなる訳です。

呼び出された bindReady() 関数は以下の処理を行います。
Mozilla 系の場合
jQuery.ready() メソッドをイベントハンドラー関数として、DOMContentLoaded イベント(注1)をバインドします。
後は、DOMContentLoaded イベントが発生した時点で jQuery.ready() メソッドが起動され、リストに登録されている関数が実行されます。
IE の場合
DOMContentLoaded イベントを実装しない IE に対して、ver 1.2.2 では新しい trick が使われました。その方法とは、func function 内に try / catch 文を設け、その try 句内で document.documentElement.doScroll("left") を起動してエラーが発生したら、その catch 句内から関数 func の再帰呼び出しを繰り返します。そして doScroll メソッドがエラーを発生しなくなったら、つまり DOMContentLoaded イベントの発生と同状態になったら、jQuery.ready()を起動します。
Opera の場合
addEventListner() 及び DOMContentLoaded イベントを利用しますが、Mozilla 系と異なるのは DOMContentLoaded イベント発生時に直ぐにjQuery.ready()メソッドを起動しないことです。
当該サイトで使用されているスタイルシートの全てが有効になるまで繰り返しチェックし、全てが有効になった時点で jQuery.ready() メソッドを実行します。
こうした手順を踏むのはおそらく、Operaではスタイルシート読み込み完了前に DOMContentLoaded イベントが発生してしまうためだと、推測されます。
safari の場合
safari も DOMContentLoaded イベントをサポートしていません。そしてsafariの場合には以上の3つのブラウザに較べて最も手間を要してDOMContent の Loaded 状態をチェックしています。
まず、document.redayState プロパティがloaded かつ complete になるまでチェックを繰り返します。
それをクリアしたら次に、header タグ内の style タグとlink タグの合計数をカウントさせ、それがdocument.stylesheets.length と一致するまで、そのチェックを繰り返します。
以上2つのチェックを終えた時点でやっと jQuery.ready() メソッドを呼び出します。
window.loaded メソッドの代替
最後に、window.loaded メソッドの代替手段(a fallback)として、addメソッドを使って window 要素に、jQuery.readyメソッドをイベントハンドラー関数とする load イベントを登録します。これはいわば最後の砦、最後の手段を用意したということでしょう。
何らかの理由によりそれまでのチェックの全てが無効だった場合には、window が loaded になった時点で、bindReady() メソッド が jQuery.ready() メソッドを起動させて所定の関数を起動させることになるのですから。
なお jQuery.ready() メソッドによる所定の関数の二重起動の心配はありません。bindReady()関数の冒頭に設置された変数 readyBound がそれを防止してくれます。このメソッドは一度起動されれば、unloadされるまで再び機能することはありません。
bindReady() 関数から呼び出された jQuery.ready() クラスメソッドは、次の処理を行います。
  • もし jQuery.isReady プロパティが false のままであれば true に変更します。
  • readyList に登録されている関数があれば、これを順次実行します。
  • 実行し終えたら readyList プロパティを nullとし、実行を待つ関数リストを空白とします。
  • 最後に、ver 1.2.2 で追加された $(document).bind("ready",func) が使用されていれば ready イベントを励起させて、func 関数を実行します。

各種メソッドのコード一覧

2237 ready: function(fn) {
2238  // Attach the listeners
2239  bindReady();
2240
2241  // If the DOM is already ready
2242  if ( jQuery.isReady )
2243   // Execute the function immediately
2244   fn.call( document, jQuery );
2245
2246  // Otherwise, remember the function for later
2247  else
2248   // Add the function to the wait list
2249   jQuery.readyList.push( function() { return fn.call(this, jQuery); } );
2250
2251  return this;
2252 }

▲ToTop

2282:var readyBound = false;
2283:
2284:function bindReady(){
2285:  if ( readyBound ) return;
2286:  readyBound = true;
2287:
2288:  // Mozilla, Opera (see further below for it) and webkit nightlies currently support this event
2289:  if ( document.addEventListener && !jQuery.browser.opera)
2290:   // Use the handy event callback
2291:   document.addEventListener( "DOMContentLoaded", jQuery.ready, false );
2292:
2293:  // If IE is used and is not in a frame
2294:  // Continually check to see if the document is ready
2295:  if ( jQuery.browser.msie && window == top ) (function(){
2296:   if (jQuery.isReady) return;
2297:   try {
2298:    // If IE is used, use the trick by Diego Perini
2299:    // http://javascript.nwbox.com/IEContentLoaded/
2300:    document.documentElement.doScroll("left");
2301:   } catch( error ) {
2302:    setTimeout( arguments.callee, 0 );
2303:    return;
2304:  }
2305:  // and execute any waiting functions
2306:  jQuery.ready();
2307: })();
2308:
2309: if ( jQuery.browser.opera )
2310:  document.addEventListener( "DOMContentLoaded", function () {
2311:   if (jQuery.isReady) return;
2312:   for (var i = 0; i < document.styleSheets.length; i++)
2313:    if (document.styleSheets[i].disabled) {
2314:     setTimeout( arguments.callee, 0 );
2315:     return;
2316:    }
2317:   // and execute any waiting functions
2318:   jQuery.ready();
2319:  }, false);
2320:
2321: if ( jQuery.browser.safari ) {
2322:  var numStyles;
2323:  (function(){
2324:   if (jQuery.isReady) return;
2325:   if ( document.readyState != "loaded" && document.readyState != "complete" ) {
2326:    setTimeout( arguments.callee, 0 );
2327:    return;
2328:   }
2329:   if ( numStyles === undefined )
2330:    numStyles = jQuery("style, link[rel=stylesheet]").length;
2331:   if ( document.styleSheets.length != numStyles ) {
2332:    setTimeout( arguments.callee, 0 );
2333:    return;
2334:   }
2335:   // and execute any waiting functions
2336:   jQuery.ready();
2337:  })();
2338: }
2339:
2340: // A fallback to window.onload, that will always work
2341: jQuery.event.add( window, "load", jQuery.ready );
2342:}
2343:

▲ToTop

2256: isReady: false,
2257: readyList: [],
2258: // Handle when the DOM is ready
2259: ready: function() {
2260:  // Make sure that the DOM is not already loaded
2261:  if ( !jQuery.isReady ) {
2262:   // Remember that the DOM is ready
2263:   jQuery.isReady = true;
2264:
2265:   // If there are functions bound, to execute
2266:   if ( jQuery.readyList ) {
2267:    // Execute all of them
2268:    jQuery.each( jQuery.readyList, function(){
2269:     this.apply( document );
2270:    });
2271:
2272:    // Reset the list of functions
2273:    jQuery.readyList = null;
2274:   }
2275:
2276:   // Trigger any bound ready events
2277:   jQuery(document).triggerHandler("ready");
2278:  }
2279: }
注1 DOMContentLoaded について

このイベントは Mozilla 系と Opera には実装され、IE とsafari は未実装です。

こちら 第30回 JavaScriptの動作を軽くするための工夫:ITpro が大変参考になります。

jQuery()の挙動を解読する(20) $(args).unbind()、$.event.remove()、$.removeData() 解読 ──jQuery解読(31)

$(args).unbind() 【ver1.2.2で変化なし】について

イベントの登録メソッドに較べて、それを削除する $(args).unbind() は余り使用頻度の高いメソッドではないでしょう。登録したイベントハンドラーをわざわざ解除する必要性は、決して高いとは思えないからです。

そのことはさて置き、$(args).unbind() メソッドは僅か数行のもので、全ての処理を $.event.remove() メソッドに委ねており、更に $.event.remove() メソッドの骨格は $(args).data() と $(args).removeData() メソッドによって構成されています。

ここでも $(args).add() メソッド同様に、本家サイトで Internals ジャンルに分類されている Data Cache を取り扱うメソッドが不可欠となっています。

$(args).unbind() のコードは以下の通りです。対象要素ノード毎に $.event.remove() を呼び出してその結果を受け取っています。

2199: unbind: function( type, fn ) {
2200:  return this.each(function(){
2201:   jQuery.event.remove( this, type, fn );
2202:  });
2203: },

$.event.remove() 【ver1.2.2で部分的に変更された】について

引数と変数指定

unbind() メソッドから3つの引数を受け取り、実際にイベントをアンバインドするメソッドです。

elem がテキストノードかコメントだった場合には何もせずに終わります。

1902 行の data() メソッドにより、当該対象要素ノードに対応する Namespaced event handlers をData Cache から抽出し、変数 events に代入します。なお、変数を3つ指定していますが、index はどこでも使われていません。

1896: // Detach an event or set of events from an element
1897: remove: function(elem, type, handler) {
1898:  // don't do events on text and comment nodes
1899:  if ( elem.nodeType == 3 || elem.nodeType == 8 )
1900:   return;
1901:
1902:  var events = jQuery.data(elem, "events"), ret, index;
1903:

ここで、events 変数には何が代入されるのか、視覚的にも明らかにしておくことが得策だと思います。

下図はあるサイトのある要素ノードに、クリックとダブルクリックの2つのイベントハンドラーをバインドした時の、Namespaced events handlers と jQuery.js が呼んでいるオブジェクトの一部、つまり jQuery.cache オブジェクトの一部を切り取ったものです。

この図にある events オブジェクトと同じような構造の、「 element 」要素ノードに対応するオブジェクトが 1651 行の変数 events に代入されます。

Namespaced_Eents_Object
jQuery ネームスペース内のイベントハンドラー
   // 当該対象ノードにイベントハンドラーが登録されていれば
1904: if ( events ) {

 /* 削除その1
  * 第2引数の type が与えられていない場合の処理である。
  * この場合には、events オブジェクト内の全ての type プロパティ、つまり登録
  * されている全てのイベントハンドラー毎に、remove() メソッドを再帰呼び出し
  * する。再帰的に呼び出す場合には、events オブジェクトから type 名称を取り
  * だして第2引数としている。
  * このブロックの処理によって、対象要素ノードに登録されている Namespaced 
  * イベントハンドラーの全てを削除することになる。
  */
1905:  // Unbind all events for the element
1906:  if ( types == undefined )
1907:   for ( var type in events )
1908:    this.remove( elem, type );

 /* イベントオブジェクトが引数となった場合の処理
  * 「type.type がある場合」とは、何らかの事情によって第2引数の type が
  * 文字列ではなく、イベントオブジェクトであった場合を指している。
  * その場合には、handler にはイベントオブジェクトの handler プロパティを
  * type にはイベントオブジェクトのtypeプロパティを代入している。
  */
1909:  else {
1910:   // types is actually an event object here
1911:   if ( types.type ) {
1912:    handler = types.handler;
1913:    types = types.type;
1914:   }
 /* の処理
  * 複数イベント一括 bind() メソッド に呼応して、unbind()メソッドもマルチ
  * イベント対応としている。
  * つまり、各イベントタイプ毎にイテレートして登録解除を進めることになる。
  * type にはイベントtype名が入る。
  */
1916: // Handle multiple events seperated by a space
1917: // jQuery(...).unbind("mouseover mouseout", fn);
1918: jQuery.each(types.split(/\s+/), function(index, type){
1919:  // Namespaced event handlers
1920:  var parts = type.split(".");
1921:  type = parts[0];
 /* 削除その2
  * 次は第2引数の type が与えられていて、それが events オブジェクトの
  * プロパティと一致する場合── if ( events[type] ) ──の処理である。
  * 1925行……第3引数 handler が与えられていれば、
  * 1926行……登録されているプロパティ=ハンドラー関数を削除する。
  * ここでは type 名のオブジェクトは削除せず、その handler 名の関数
  * だけを削除する。
  * ここに handler.guid プロパティの値はハンドラー関数である。
  * ※ event.add() メソッド参照。
  */
1923:  if ( events[type] ) {
1924:   // remove the given handler for the given type
1925:   if ( handler )
1926:    delete events[type][handler.guid];
1927:
  * 削除その3
  * 第3引数の handler が与えられていない場合には(1929行以下)
  * events[type] オブジェクト内の各プロパティに対して(1930行)
  * カスタムイベントがないか(!parts[1])、又は登録されているカスタムイベ
  * ントタイプと、指定されたカスタムイベントタイプが一致すれば(1932後段)、
  * handler プロパティを削除する。
  * これにより指定した type で登録されているイベントハンドラー関数を
  * 全て削除する。
  */
1928:   // remove all handlers for the given type
1929:   else
1930:    for ( handler in events[type] )
1931:     // Handle the removal of namespaced events
1932:     if ( !parts[1] || events[type][handler].type == parts[1] )
1933:      delete events[type][handler];
1934:
一般のイベントハンドラーの削除
 /* event[type] オブジェクト内にプロパティが残ってないかチェックする。
  * もし、残っていれば for loop を 中止する。
  */
1935:   // remove generic event handler if no more handlers exist
1936:   for ( ret in events[type] ) break;
 /* 削除その4
  * もし、 event[type] オブジェクト内にプロパティが残っていれば、 
  * !ret は false となるので 1938行以下は実行されない。
  * さて、もはや eventsオブジェクトに type プロパティが存在しない場合
  * ──if ( !ret )──には、一般的なイベントハンドリングによって登録した
  * ハンドラー関数の登録を解除する。
  * ここに jQuery.data(element, "handle") はイベントハンドラー登録時に利用
  * される代理母的な関数である。既に add() 及び handle() メソッドの項で述べ
  * たように、add()メソッドによってNamespaced イベントハンドラーに登録済みの
  * 関数から、jQuery.event.handle() メソッドがバインドする関数を選択し登録する。
  */
1937:   if ( !ret ) {
1938:    if ( !jQuery.event.special[type] || jQuery.event.special[type].teardown.call(elem) === false ) {
1939:     if (elem.removeEventListener)
1940:      elem.removeEventListener(type, jQuery.data(elem, "handle"), false);
1941:     else if (elem.detachEvent)
1942:      elem.detachEvent("on" + type, jQuery.data(elem, "handle"));
1943:    }
1944:    ret = null;
1945:    delete events[type];
1946:   }
1947:  }
1948:  });
1949: }
 /* 削除その5
  * 最後の処理は再び Data Cache オブジェクト内のクリーニングである。
  * これまでの処理で全てのイベントハンドラー(タイプ、カスタムイベント、
  * ハンドラー関数、id 番号など)を削除するが、まだ Data Cache オブジェク
  * ト内には残存物がある。それは events と handle オブジェクトである。
  * これらを以下の数行で削除する。
  * ret がないことつまり events オブジェクト内が空であることを確認して、
  * events と handle オブジェクトを削除する。
  * 以上の結果、当該要素ノードelementに関してData Cache オブジェクト内に
  * 存在するオブジェクトは、当該要素ノードに対応するuuid番号を振られた空
  * のオブジェクトだけとなる。
  */
1951:   // Remove the expando if it's no longer used
1952:   for ( ret in events ) break;
1953:    if ( !ret ) {
1954:     var handle = jQuery.data( elem, "handle" );
1955:     if ( handle ) handle.elem = null;
1956:      jQuery.removeData( elem, "events" );
1957:      jQuery.removeData( elem, "handle" );
1958:     }
1959:  }
1960: },

▲ToTop

jQuery.removeData( elem,name ) 【 ver1.2.2 で変化なし】解読

jQuery.removeData() メソッドは、jQuery.data()メソッドで構築した Data Cache オブジェクト内のオブジェクトを削除するためのもので、jQuery.event.remove()メソッドの最後に登場します。その働きは既に上で述べたので、ここでは当該メソッドの解読を行ってこのエントリイを閉めます。

コード全体は name 引数がある場合とない場合の2通りに大別されます。

変数初期化
668: removeData: function( elem, name ) {
669:  elem = elem == window ?
670:    windowData :
671:    elem;
672:

 /* data() メソッドで elem, name を登録する時に、elemには、expando
  * プロパティが付与される。
  * ( data() メソッドの 518行 id = elem[ expando ] = ++uuid;)
  * ここではこれを取りだして改めて地域変数 id に代入している。
  * なお、この id は先の 518 行により、個々の elem に対応して
  * cache オブジェクト内のプロパティとして割り振られているので、
  * cache[id]とすれば 当該 uuid番号 のプロパティ値を指すことになる。
  * 勿論 elem の指定ミスがあればこの行は通過し、540行でも撥ねられ、
  * その先に進んでも全てスルーする。
  * また name がないか間違っている場合も全ての行がスルーされる。
  */
673:  var id = elem[ expando ];
674:
削除その1──cache.id オブジェクト内のプロパティ削除
 /* 抽出された id 番号を頼りに、その中にある name プロパティを削除する。
  * 1697、1698 行の引数に置き換えれば events と handle を削除することに
  * なります。
  */
675:  // If we want to remove a specific section of the element's data
676:  if ( name ) {
677:   if ( jQuery.cache[ id ] ) {
678:    // Remove the section of cache data
679:    delete jQuery.cache[ id ][ name ];
680:
削除その2── element に対応する id 番号名のオブジェクト削除
 /* id オブジェクト内に何もないことを確認し(546行)、
  * なければ(547行)、elem つまり当該 elementに対応する id 番号名のオブ
  * ジェクトそのものを、removeData()を再帰呼び出しして削除する。(548行)
  * なお 548 行を 564 行と同様のコードとしなかったのは、別の name 名のオブ
  * ジェクトが残っているケースもあるため、これに配慮したものと推測される。
  * 例えば、events オブジェクトを削除する段階では、まだ handle オブジェクト
  * が残っている。(1697-1698行参照)
  */
681:    // If we've removed all the data, remove the element's cache
682:    name = "";
683:
684:    for ( name in jQuery.cache[ id ] )
685:     break;
686:     if ( !name )
687:     jQuery.removeData( elem );
688:   }
689:
削除その3──elemオブジェクトのexpando プロパティ削除 及び cache 内の elem id プロパティ削除
 /* name 引数が与えられなかった場合の処置
  * elem の expando プロパティ削除を行う(556行)。
  * IE ではエラーが発生するのでDOMの removeAttribute メソッドを使って
  * elem の expando プロパティを削除する。
  * 最後に cache オブジェクト内の id プロパティを削除し、elem に対応する
  * cache オブジェクト内の全ての痕跡を消し去る。
  */
691:  // Otherwise, we want to remove all of the element's data
692:  } else {
693:   // Clean up the element expando
694:   try {
695:    delete elem[ expando ];
696:   } catch(e){
697:    // IE has trouble directly removing the expando
698:    // but it's ok with using removeAttribute
699:    if ( elem.removeAttribute )
700:     elem.removeAttribute( expando );
701:   }
702:
703:   // Completely remove the data cache
704:   delete jQuery.cache[ id ];
705:  }
706: },

jQuery()の挙動を解読する(19) jQuery.extend()メソッドのバグについて ──jQuery解読(30)

バグ修正が施された jQuery.1.2.2b2.js をゲット

jQuery() の挙動を解読する(3) jQuery.extend()及びjQuery.prototype.extend()──jQuery解読(7) において、jQuery.extend() メソッドの deep copy 部分にバグがあることを示唆しました。

そのバグは通常のメソッド拡張やオブジェクト拡張の場合には出現しないものなので、直ぐに発覚しなかったのでしょう。また、そもそも deep copy を利用するユーザーが余り多いとは思えないので、その点からも発覚しにくかったものと思われます。

さて、別件で本家サイトを閲覧していて、「ところでこのサイトは、どの版の jQuery.js を利用しているのだろうか?」とふと疑問に思い、本家サイトが利用している版を調べました。

すると、それは公開されている最新版である 1.2.1 ではなく、1.2.2b2 という版でしたので早速ゲットし、jQuery.extend()メソッドに変更が加えられてないかどうか確認してみました。

案の定 jQuery.extend()メソッドには大幅に手が加えられていた

1.2.2b 版の jQuery.extend() メソッドは、利用変数名が大幅に変更され、かつ指摘したバグ箇所には修正が施されていました。

修正箇所の詳細は別途述べるとして、参考のために 1.2.2b2 版の jQuery.extend() メソッドを以下に掲載しておきます。

なお、ここで触れている1.2.2b2 版は Date: 2007-12-20 10:14:13 -0500 付けのものです。直近のnightly 版は同じ1.2.2b2 ですが、日付は Date: 2008-01-14 14:17:35 -0500 となっています。

1.2.2b2 版 jQuery.extend() メソッドコード

jQuery.extend = jQuery.fn.extend = function() {
 // copy reference to target object
 var target = arguments[0] || {}, i = 1, length = arguments.length, deep = false, options;

 // Handle a deep copy situation
 if ( target.constructor == Boolean ) {
  deep = target;
  target = arguments[1] || {};
  // skip the boolean and the target
  i = 2;
 }

 // Handle case when target is a string or something (possible in deep copy)
 if ( typeof target != "object" && typeof target != "function" )
  target = {};

 // extend jQuery itself if only one argument is passed
 if ( length == 1 ) {
  target = this;
  i = 0;
 }

 for ( ; i < length; i++ )
  // Only deal with non-null/undefined values
  if ( (options = arguments[ i ]) != null )
   // Extend the base object
   for ( var name in options ) {
    // Prevent never-ending loop
    if ( target === options[ name ] )
     continue;

    // Recurse if we're merging object values
    if ( deep && options[ name ] && typeof options[ name ] == "object" && target[ name ] && !options[ name ].nodeType )
     target[ name ] = jQuery.extend( target[ name ], options[ name ] );

    // Don't bring in undefined values
    else if ( options[ name ] != undefined )
     target[ name ] = options[ name ];

   }

 // Return the modified object
 return target;
};

jQuery()の挙動を解読する(18) $(args).trigger()、$(args).triggerHandler()、$.event.trigger() 解読 ──jQuery解読(29)

$(args).trigger()、$(args).triggerHandler()

これらのメソッドに該当するコードは jQuery.js ver1.2.2 の 2205-2215 行です。2214 行だけが1.2.1とことなる箇所で新規追加行になります。バグフィックスと言うところでしょう。

2205: trigger: function( type, data, fn ) {
2206:  return this.each(function(){
2207:   jQuery.event.trigger( type, data, this, true, fn );
2208:  });
2209: },
2210:
2211: triggerHandler: function( type, data, fn ) {
2212:  if ( this[0] )
2213:   return jQuery.event.trigger( type, data, this[0], false, fn );
2214:  return undefined;
2215: },

これらのインスタンスメソッドは、全く同様の引数をとり、共に対象要素に登録されている、type で指定されたイベントタイプに対応するイベントハンドラー関数を起動します。しかし、微妙に異なる引数(上のピンク色の2箇所)を $.event.trigger() に渡してこのクラスメソッドを起動し、このメソッドに全ての処理を委ねた上で処理結果を return します。したがってこれらの2つのインスタンスメソッドの挙動を知るには $.event.trigger メソッドを理解する必要があります。

しかしその前に、2つのメソッドの差異を外観しておきます。

  • trigger インスタンスメソッドはその対象要素の各々に対して、jQuery.event.trigger メソッドを適用し、各々の結果を返します。他方、triggerHandler インスタンスメソッドは、対象要素の最初の1つの要素に対してだけ作用し結果を返します。 return 値は全て jQuery インスタンスです。
    なお、何故対象ノードを複数( trigger )と1つだけ( triggerHandler )に分けたのか、その理由は分かりません。triggerHandler() メソッドでも trigger() メソッド同様に、複数の要素ノードを対象として作動するようにしても問題はないと思われるのですが..。
  • trigger インスタンスメソッドは、対象要素に登録され、type で指定されたイベントタイプに対応するイベントハンドラー関数を起動し、かつ jQuery.event.trigger メソッドに渡される4番目の引数を true とすることにより、当該イベント発生時のブラウザ固有の動きを抑制しません。
  • 他方、triggerHandler インスタンスメソッドは、対象要素に登録されているイベントタイプに対応するイベントハンドラー関数を起動させる点は trigger メソッドと同様ですが、jQuery.event.trigger メソッドに渡される4番目の引数を false とすることにより、type で指定されたイベントタイプに対応するブラウザ固有の動きを抑制します。

▲ToTop

$(args).trigger() と $(args).triggerHandler() の実例

更に、$.event.trigger() クラスメソッドの解読を行う前に、これらのインスタンスメソッドの実例を見ておきます。これらのメソッドが何を行うのか理解することが先決であり、本家 jQuery サイトにいくつかの例示がありますが、第三引数の fn を与えた例はなく、また例そのものが分かりやすいとは言えないからです。

独自に作成した $(args).trigger() メソッドの実例

例は、$(args).trigger() メソッドの引数が1つだけ、2つ及び3つの場合の各々の違いが分かるように3パターン作成し、更にtriggerされるイベントタイプも click、dblclick 及び mouseover の3種類とし、合計9つのパターンの例を作成しました。

複雑な挙動をするtrigger()インスタンスメソッドを十分に理解するには、これだけの例が必要でした。

使い方

  • 下のカラーボックス上ではそれぞれの説明にあるマウスイベントタイプが発生します。この場合に起こることは、それぞれのボックスにバインドされたイベントハンドラーが、各々のイベント発生によって働いただけに過ぎません。
  • これらの3つのボックスには同じ class 名 "Box" が振ってあり、トリガーメソッドの対象としてこの class 名をもつ3つのボックスを指定してあります。つまり jQuery(".Box").trigger(・・・)です。trigger() メソッドはこれらの3つの要素ノードにバインドされた、異なるイベントタイプのイベントハンドラーを励起させ得ることになります。
  • カラーボックスの下にあるボタンが triggerメソッドを起動させる起動ボタンです。縦4行、横3列の10個のボタンがありますが、一番下のボタンは初期化ボタンで、他の各行は trigger メソッドの引数が異なり、各列はトリガーするイベントが異なります。各々はボタン名に書いてあるような設定となっており、各々のボタンに記されているような trigger() インスタンスメソッドがバインドされています。これらのボタンをクリックすると各々にバインドされた異なる trigger メソッドが呼び出され、trigger対象要素ノードにバインドされているイベントハンドラーをトリガー(励起)させます。
  • 1行目のボタンをクリックすると、3つの trigger メソッド対象要素ノードから、対象となるイベントタイプがバインドされている要素ノードだけのイベントがトリガーされます。当該イベントタイプがバインドされていない他の2つの要素ノードのイベントハンドラーは起動されません。
  • 2行目のボタンには第2引数が与えられています。ここでも対象となるイベントハンドラーがバインドされている要素ノードだけが反応します。第2引数はトリガー先のイベントに情報などを付加する働きをし、各々のボタンをクリックするとボックスの下に文字列が現れます。これが付加された情報です。なお、再びクリックすると付加された文字列が消えるような toggle() メソッドが設定されています。
  • 3行目のボタンは、3つの引数を与えた trigger メソッドをバインドしたものです。このボタンで初めて、triggerメソッドの対象要素ノードが3つのボタンであったことが明示的に明らかにされます。第3引数の fn 関数はイベントタイプに無関係に triggerメソッドの対象要素ノードの全てに作用するため、3行目のどのボタンをクリックしても、3つのボックスの背景色が同一色に変化します。
  • しかし、その場合であっても trigger メソッドは、第1引数で指定されたイベントタイプがバインドされた要素ノードのイベントハンドラー関数だけを起動させるので、当該イベントタイプがバインドされた要素ノードのイベントハンドラーしか起動されません。
    ここで興味深いことが確認できます。ブルーボックスには click イベントがバインドされており、3行のボタンの第1列に登録されている trigger メソッドは イベントタイプ click を起動します。
    この結果、clickイベントがバインドされているボックス、ブルーボックス及びレッドボックスのイベントハンドラーが反応します。(レッドボックスにはダブルクリックで付加される文字を消すためにクリックイベントがバインドされています。)
    従って、例えばレッドボックスをダブルクリックした後に、第1列目の3つのいずれかのボタンをクリックすると、ブルーとレッドの2つのボックスのクリックイベントが励起(トリガー)されます。レッドボックスのその他の場合によるダブルクリック後においても同様です。
Box A:このボックスを click すると一旦透明化され、その後元に戻ります。
Box B:このボックスを double click するとボックス内に文字が増え、その後このボックスをクリックすると増えた文字が消えます。

Box C:このボックスに mouseover し続けると透明度が変化しながら上下にスライドし続けます。
  

▲ToTop

$(args).triggerHandler() メソッドの実例

triggerHandler() メソッドは、後に $.event.trigger() メソッドの解読でその仕組みを明らかにしますが、trigger メソッドと異なるのは、ブラウザ既定の動作を停止させる点です。

ですから、既定の動作を持たないタイプのイベントがバインドされた要素ノードを対象として、このメソッドを適用しても何も意味がありません。

ここに既定の動作を持つイベントタイプには何があるのか、その点は不明です。分かっていることはfocus()、blur()、click()などの、a タグやinput、textarea タグなどが元々有しているメソッド名と一致するイベントタイプが「ブラウザ既定の動作を有する」ということが出来るであろうことです。

さて、実例を作ってみようとしたのですが、jQuery本家サイト以上のそれは作れませんでしたので、本家サイトの例を引用して解説しようと思います。

本家サイトからの引用サンプル

  
  1. input ボックスにはイベントタイプ focus のイベントハンドラーが登録されています。ハンドラー関数によりフォーカスが input ボックスに移ると Focused! という文字を表示してから、フェードアウトします。
    そのことを確認するには input ボックス内でマウスをクリックすれば分かります。その末尾に Focused! と表示され、その後1.5秒掛けてフェードアウトします。これが設定されている focus イベントの発生と実行です。
  2. 次に、マウスを input ボックス外でクリックしてフォーカスをはずしてから、Button1 をクリックします。すると inputタグ既定の focus イベントが起動して、 Focused! と表示し、かつ trigger されたイベントハンドラーが Focused! と表示します。つまり2つの Focused! が表示されます。そして共に1.5秒間でフェードアウトします。この時、input ボックスがフォーカスされ、マウスカーソルがその中で点滅していることを確認できます。
    ここでは、input タグに設定された focus イベントのデフォルト動作(=フォーカスすると言う動作)が trigger() メソッドによって行われ、同時に trigger() メソッドによってイベントハンドラー関数が起動されたため、2つの文字が表示されたことになります。
    なお、これまでのことはIEでの確認です。Firefox と Opera ではこの違いが出現せず、trigger() でも triggerHandler() でも結果は変わりませんでした。理由は不明です。
  3. 次に、念のために input ボックス外でクリックしてフォーカスをはずしてから、Button2 をクリックします。すると focus イベントハンドラーが起動して、Focused! と表示され1.5秒間でフェードアウトします。しかし、今度は input タグ内にフォーカスは当たっていません。input ボックス内でマウスカーソルは点滅していません。また表示される Focused! も1つだけです。
    これで triggerHandler() メソッドと trigger() メソッドとの効果の違いが確認出来ました。

▲ToTop

$.event.trigger()

愈々、trigger() 及び triggerHandler() がどのようにして作用しているのか、その挙動を探ります。これらの2つのメソッドから呼び出される $.event.trigger() を解読します。

$.event.trigger() の引数について

trigger() クラスメソッドは type、data、elem、donative 及び extra の5つの引数を取ります。

これらは順に、

  • tyoe: 励起させたいイベントタイプ
  • data: type イベント発生時のイベントオブジェクト並びに trigger により励起させるハンドラー関数で利用させる引数によって構成される配列オブジェクト
  • elem: type イベントを発生させる要素ノード(トリガー先とでも言うべきか)
  • donative: typeイベントによるブラウザのデフォルト動作を行わせるか(true)、行わせないか(false)を指定する真偽値
  • extra: trigger() メソッドによって type イベントを発生させた時に起動させる、イベントハンドラー関数とは別の、独自に指定する関数

を意味しています。

1. 第2引数 data の処理

data 引数を配列にし、data がなければ空の配列を作り data に代入する。data には trigger メソッドを起動させたイベントオブジェクトなどを記述する。

1962: trigger: function(type, data, elem, donative, extra) {
1963:  // Clone the incoming data, if any
1964:  data = jQuery.makeArray(data || []);
1965:
2. トリガー対象 (elem) が指定されていない場合の処理

第3引数の elem がない場合の処理です。この場合において、当該頁にイベントが登録されていれば、全てのノードを対象としてtrigger()メソッドを再帰呼び出しし、イベントが全く登録されていなければ何もしません。

1966:  // Handle a global trigger
1967:  if ( !elem ) { //elem が指定されていなければ
1968:   // Only trigger if we've ever bound an event for it
1969:   if ( this.global[type] ) //typeイベントが登録されていれば
      // 全てのノードを対象として trigger クラスメソッドを再帰呼び出しする。
1970:    jQuery("*").add([window, document]).trigger(type, data);
1971:
3. 1つの対象ノードに対する処理 (1) イベントハンドラー起動前の処理
1972:  // Handle triggering a single element
    // 1の要素ノードを trigger 対象とする。
1973:  } else {

 /* elem がテキストやコメントだった場合
  * 何もせずに undefined を返す。
  */ 
1974: // don't do events on text and comment nodes
1975: if ( elem.nodeType == 3 || elem.nodeType == 8 )
1976: return undefined;
1977:
 /* elem[type] が関数ならば fn = true、違えば fn = false とする。
  * この elem[type] は突然登場し、このメソッド以外では使われていない。
  * これはブラウザ固有の type メソッドが elem に指定されている場合に
  * 部分であり、2010-2017 行に連動する。
  */
1978:   var val, ret, fn = jQuery.isFunction( elem[ type ] || null ),

 /* data 配列が空ならば、あるいは data 配列の最初の要素に preventDefault
  * プロパティがなければ(つまりそれがイベントオブジェクトでなければ)
  * evt = true とする。その他の場合には evt = false とする。
  * こうすることにより、イベントオブジェクトが第一引数にない場合において
  * var evt = true としてダミーイベントを用意する準備をしている。
  */
1979:   // Check to see if we need to provide a fake event, or not
1980:   evt = !data[0] || !data[0].preventDefault;
1981:

 // ダミーのイベントオブジェクトを作って data 配列の先頭要素とする。
 // その type プロパティは type、target プロパティは elem とする。
1982:   // Pass along a fake event
1983:   if ( evt )
1984:    data.unshift( this.fix({ type: type, target: elem }) );
1985:

 /* data の最初の要素としてユーザーが指示したイベントオブジェクト、
  * 又は 1721 行で作ったダミーのイベントオブジェクトの、type 
  * プロパティを type とする。
  * これは専らダミーイベントへの対応策であって、イベントオブジェクトの
  * type プロパティを与える目的のための行である。そうしないとダミーイベ
  * ントとして用を為さないから。
  */
1986:   // Enforce the right trigger type
1987:   data[0].type = type;
1988:
4. 1つの対象ノードに対する処理 (2) イベントハンドラー関数の起動その1

elem に対応する cache.id オブジェクトの "handle" プロパティ値が存在し、それが関数であれば、data を引数とする関数を elem のメソッドとして実行する。ここに data[0] はこれまでの処理でイベントオブジェクトとなっている。

これにより指定した type イベントに対応して登録されているイベントハンドラー関数が呼び出され、実行される。なお、当該関数の返値がvalに代入される。

1989:   // Trigger the event
1990:   if ( jQuery.isFunction( jQuery.data(elem, "handle") ) )
1991:    val = jQuery.data(elem, "handle").apply( elem, data );
1992:
5. 1つの対象ノードに対する処理 (3) イベントハンドラー関数の起動その2

(1)fn が存在せず、(2)elemに "on"+type 属性があり、(3)その属性値である関数を data を引数として実行して return 値が false ならばval に false を代入する。

これにより"on"+type 属性値である関数が励起・実行される。しかし、この行の起動を確認することは出来なかった。

1993:   // Handle triggering native .onfoo handlers
1994:   if ( !fn && elem["on"+type] && elem["on"+type].apply( elem, data ) === false )
1995:   val = false;
1996:
6. 1つの対象ノードに対する処理 (4) 固有に指定した関数 extra の起動
 // まずダミーイベントが設定されていれば、それを data 配列から削除する。
1997:   // Extra functions don't get the custom event object
1998:   if ( evt )
1999:    data.shift();
2000:

 /* 次の部分は ver 1.2.2で大きく変わった箇所です。
  * extra が既述されていてそれが関数の場合に、 val の値があれば data 配列に
  * その値を結合し、なければ data のままで、その配列を引数として当該関数を実
  * 行し、retに実行結果を返す。
  * この場合、1998-1999 行によりダミーイベントは削除されている一方、最初に
  * data にイベントオブジェクトが記述されていれば、それが引数として使われる。
  */
2001:   // Handle triggering of extra function
2002:   if ( extra && jQuery.isFunction( extra ) ) {
2003:    // call the extra function and tack the current return value on the end for possible inspection
2004:    ret = extra.apply( elem, val == null ? data : data.concat( val ) );

 /* もし何かが返されれば、それを優先的に val に代入してvalを上書きする。
2005:    // if anything is returned, give it precedence and have it overwrite the previous value
2006:    if (ret !== undefined)
2007:     val = ret;
2008:   }
7. 1つの対象ノードに対する処理 (5) ブラウザ固有のメソッドの扱い

(1)fn が存在し、(2)donative が trueで、(3)val も true で、(4)elem が a 要素でなく、(5)type が click でないならば、triggerd の値を true としてから、elem[type]()メソッドを実行する。

この部分は donative に関わる部分、つまりブラウザ固有の動きをさせるか、させないかを決める部分である。

trigger() インスタンスメソッドの場合には donative = true として jQuery.event.trigger() メソッドが起動されるので、elem[type]() が実行される。しかし、triggerHandler() メソッドの場合には donative = false として jQuery.event.trigger() メソッドが起動されるため、ブラウザ固有のメソッドすなわち elem[ type ]() は起動されない。

2010:   // Trigger the native events (except for clicks on links)
2011:    if ( fn && donative !== false && val !== false && !(jQuery.nodeName(elem, 'a') && type == "click") ) {
      //次の行は1843行(in add() クラスメソッド)に関わる。
2012:     this.triggered = true;
2013:     try {
2014:	    elem[ type ]();
2015      // prevent IE from throwing an error for some hidden elements
2016      } catch (e) {}
2017:    }
2018:
2019:    this.triggered = false;
2020:  }
8. 1つの対象ノードに対する処理 (6) 終了処理
2021:
2022:  return val;
2023: },
2024:

jQuery解読──過去記事 jQuery.extend() の更新について

 二ヶ月程前にextend()メソッドの解読をしました。
 正確に言えば解読したつもりでした。


jQuery() の挙動を解読する(3) jQuery.extend()及びjQuery.prototype.extend()──jQuery解読(7)


 しかし、改めて目を通してみると、沢山のミスが発覚しましたので、全面更新しました。
 お読みいただいた方々には大変ご迷惑をお掛けいたしました。
 よろしければ改めて読んでやってください。

jQuery()の挙動を解読する(17) $.event.handle()、$.event.fix() 解読──jQuery解読(28)

$.event.handle() 【 ver1.2.2では可読性を高める変更以外の変更なし 】 解読

このメソッドはあるイベントが発生した時に、それに対応するイベントハンドラー関数をイベントハンドラーリストから選択/抽出し、かつ実行させるメソッドです。なお、このメソッドの起動は次のように複雑な過程を辿ります。

イベント発生時には、(1) addEventListner() メソッド 又は attachEvent() メソッドから add() メソッドによって定義された地域変数 handle が呼び出され、(2) この handle に data()メソッドによって代入された関数( jQuery.event.handle.apply(element,auguments)……1620行 )が起動され、そこから jQuery.event.handle() メソッドが呼び出されます。

2025: handle: function(event) {
2026:  // returned undefined or false
2027:  var val; //undefined
2028:

 /* 2029 行はevent オブジェクトの正規化とでも呼ぶことが相応しいコードです。
  * (1) event || window.event || {}  で引数 event をクロスブラウザ対応とし、
  * (2) その結果を引数として fix に渡して、イベントオブジェクトを正規化する。
  * ここに「正規化」とは W3C 準拠の表現で統一することを意味しており、つまり
  * 各種のイベントオブジェクトのプロパティとメソッドをクロスブラウザ対応表現
  * に統一することを指している。
  * 左辺のオブジェクト event には、プロパティとメソッドの全てがクロスブラウザ
  * 対応済みに補正されたイベントオブジェクトが代入される。
  */
2029:  // Empty object is for triggered events with no data
2030:  event = jQuery.event.fix( event || window.event || {} );
2031:

 /* 正規化前も後も、event オブジェクトには type プロパティが存在している。
  * 但し、このプロパティには、カスタム名称付き(存在していれば)のイベント
  * タイプ名称が add() メソッドにより代入されている。1762-1763 行はこのプロ
  * パティ値からカスタム文字列を排除し、イベント名称だけを取り出すものである。
  * event.type プロパティ には、標準のイベント名文字列だけが代入される。
  */
2032:  // Namespaced event handlers
2033:  var parts = event.type.split(".");
2034:  event.type = parts[0];
2035:

 /* 2つの data() メソッドによって、jQuery.cache オブジェクト内に登録済みの
  * イベントハンドラー(イベントタイプと data プロパティ付きのイベントハンド
  * ラー関数)を検出し、そのイベントハンドラー関数([event.type]で特定される)
  * を地域変数 c に代入する。(※ この「検出」過程は別途詳細に述べる予定。)
  * 地域変数 args には引数オブジェクト arguments の2番目以降を抽出した結果を
  * 代入する。
  * 次に1766 行において 1759 行で定義した event オブジェクトを args 配列の先頭
  * に代入する。こうして正規化される前の event オブジェクトを args から除外し、
  * 替わりに 正規化済みの event オブジェクトに入れ替えることになる。
  * つまり arge[0] には正規化済みの event オブジェクトが代入される。
  */
2036:  var handlers = jQuery.data(this, "events") && jQuery.data(this, "events")[event.type], 
    args = Array.prototype.slice.call( arguments, 1 );
2037:  args.unshift( event );
2038:

 /* 或る対象ノードに登録されている1つのイベントタイプは1つ以上のハン
  * ドラー関数をとり得る。(例えば load イベントには複数のハンドラー関数
  * を登録することが多い。)そこでオブジェクトのプロパティ(つまりハンド
  * ラー関数)の逐次検出を行う。
  */
2039:  for ( var j in handlers ) {
 // 正規化済みの event オブジェクトのプロパティに、ここまでのコードで抽出
 // したイベントハンドラー関数とその data プロパティを代入しておく。
2040:   var handler = handlers[j];
2041:   // Pass in a reference to the handler function itself
2042:   // So that we can later remove it
2043:   args[0].handler = handler;
2044:   args[0].data = handler.data;
2045:

 /* 登録済みのイベントがカスタムイベントではない場合、又は抽出されたイベント
  * ハンドラー関数の type プロパティ値が所定のカスタムイベント名称に等しい場
  * 合には、抽出したイベントハンドラー関数 c[j] を args を引数として実行する。
  * c[j] 関数実行後には return 値が地域変数 tmp に代入されるが、return 文
  * がない場合には Javascript の仕様により undefined が代入される。
  */
2046:   // Filter the functions by class
2047:   if ( !parts[1] || handler.type == parts[1] ) {
2048:    var ret = handler.apply( this, args );
2049:

 /* val は 1756 行より undefined なので、型と値は false には一致しない。
  * よって 1778 行の val !== false は true となり、val に ret 値が代入
  * される。
  */
2050:    if ( val !== false )
2051:     val = ret; //つまり val も ret も 一般に undefined となる。
2052:
 // ret の型と内容が false に一致すれば、つまり handler から false が return 
 // されれば、ブラウザのデフォルト動作を抑制し(1782行)、イベントの伝搬を止める
 //(cancelBuuble = true とする)。
2053:    if ( ret === false ) {
2054:     event.preventDefault();
2055:     event.stopPropagation();
2056:    }
2057:   }
2058:  }
2059:

 // 2060 行のコメントに書いてある通り IE 対策である。
 // event オブジェクトのプロパティやメソッドに null 値を代入して
 // メモリを解放する。
2060:  // Clean up added properties in IE to prevent memory leak
2061:  if (jQuery.browser.msie)
2062:   event.target = event.preventDefault = event.stopPropagation =
2063:   event.handler = event.data = null;
2064:
2065:  return val; // undefined が返される。
2066: },

▲ToTop

$.event.fix() 解読

これまでに何度も触れてきたが、fix() メソッドは event オブジェクトをクロスブラウザ対応に正規化するためにあります。この正規化対象となっているevent オブジェクトのプロパティとメソッドは以下の通りです。

なお、クロスブラウザ対応コードの記載は省略しますが、「……(○○対応)」とは当該のコードがどのブラウザを対象として正規化を行っているかを示してます。

  1. event.preventDefault()……(IE 対応)ブラウザの既定の動作を抑制する
  2. event.stopPropagation()……(IE 対応)イベントのバブリングを止める。
  3. event.target(IE 対応Safariのテキストノードへの対応)
  4. event.target.nodeType==3(Safariのテキストノードへの対応)
  5. event.relatedTarget……(IE 対応)jQuery.js 内の hover() メソッドで利用されている(1909行)。
  6. event.pageX、event.pageY……(IE 対応)絶対座標値
  7. event.which(key イベント及びクリックボタンイベント)……(IE 対応)
  8. event.metaKey(Mac 以外への対応)
 // fix()メソッドは event オブジェクトを単一引数として定義される。
 // このオブジェクトが fix() メソッドによって正規化され、正規化された event
 // オブジェクトが fix() メソッドからreturn される。
2068: fix: function(event) {
2069: // store a copy of the original event object
2070: // and clone to set read-only properties
2071: var originalEvent = event;
 /* 上の行で元のオブジェクトの複写を作成しておき、
  * コピーされたオブジェクトのプロパティを全て無名オブジェクト{ }に複写し、
  * これを event オブジェクトに代入する。
  * 以上の仕組みは jQuery.extend() メソッドの理解を要するが、このメソッド
  * については過去に解読した【jQuery解読(7)】ので、ここでは説明を割愛する。
2072: event = jQuery.extend({}, originalEvent);
 :
 :以下略

▲ToTop

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