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()の挙動を解読する(24) jQuery 1.2.2 における jQuery 定義コードを分析する──jQuery解読(36)

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

1.2.2 ではまたしても jQuery オブジェクトの定義コードが変わりました。1.2 から 1.2.1 でも変わっていましたので、根幹部分の定義が立て続けに更新されている訳です。

1.2.2の変更によって(18行のコメントにもあるように)新たに jQuery オブジェクト(その実体は関数)は そのプロパティである init() メソッドをコンストラクタとするインスタンスを受け取るものとして位置づけられました。

 17: var jQuery = window.jQuery = function( selector, context ) {
 18:  // The jQuery object is actually just the init constructor 'enhanced'
 19:  return new jQuery.prototype.init( selector, context ) :
 20: };

ここで興味深いことは、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)コンストラクタ関数の prototypeプロパティをそのオブジェクトの prototype として設定し、(3)そのオブジェクトへの参照をキーワード this に代入し、(4)そのオブジェクトのメソッドとしてコンストラクタ関数を呼び出す」と説明されています。

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

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

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

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

しかし、ここに登場する jQuery.prototype.init.prototype には何もプロパティやメソッドを定義していません。

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

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

525 // Give the init function the jQuery prototype for later instantiation
526 jQuery.prototype.init.prototype = jQuery.prototype;

つまり、jQuery.prototype.init 関数オブジェクトのプロトタイプは jQuery 関数オブジェクトのプロトタイプに等しくなります。

これを踏まえてjQuery定義コードの19行目を整理すると、
 (1) obj(=this)作成
 (2) this.prototype = jQuery.prototype.init.prototype = jQuery.prototype設定
 (3) jQuery.prototype.init.apply(this, [selector, context])実行
となります。

こうして、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: