今回もイベントの処理に関する部分の解説になります。$(document).ready()を実装している部分が出てきますが、ここのソースコードは非常に興味深いです。よくある実装方法としては、動的にscriptタグを挿入してJavaScriptコードを実行する方法がありますが、jQueryの実装はもっと複雑なものです。また、DOMContentLoadedを利用して、処理の開始をできるだけ早くして、ユーザの体感速度を向上するための工夫も大変参考になります。
それでは、順に見ていきましょう。
jQuery.fn.bind()
jQuery.fn.bind()メソッドは、現在選択されている要素にイベントを割り当てます。ここで、第一引数のtypeには、"click"、"mouseover"などのイベント種別が渡されてきます。また、第二引数のdataには、イベントハンドラに引き継ぐデータが渡されてきます。最後に、第3引数のfnがイベントハンドラ関数になります。
2185行目で、unloadイベントの場合のみone()メソッドを呼び出し、それ以外の場合は1804行目(第8回参照)のjQuery.evnet.add()メソッドに処理を引き継ぎます。
jQuery.fn.one()
jQuery.fn.one()メソッドは、引数のイベントハンドラ関数fnを1回だけ実行するように割り当てます。
2192行目でjQuery.event.add()メソッドを呼び出すのは先ほどのbind()メソッドと同様ですが、2193行目でハンドラ関数の前にunbind()によってイベントを解除するように設定しているところが違います。
jQuery.fn.unbind()
jQuery.fn.unbind()メソッドは、登録されているイベントを解除します。実際の処理は、2201行目で呼び出しているjQuery.event.remove()メソッドによって実行されます。
jQuery.fn.trigger()
jQuery.fn.trigger()メソッドは、jQuery.event.trigger()メソッドのラッパーです。2207行目で、各要素に指定されたイベントを実行します。
jQuery.fn.triggerHandler()
jQuery.fn.triggerHandler()メソッドも、指定されたイベントを実行するのですが、ブラウザ標準のアクションを実行しないところが違います。このため、2213行目でjQuery.event.trigger()メソッドを呼び出す際に第4引数としてfalseを指定します。
jQuery.fn.toggle()
jQuery.fn.toggle()メソッドは、引数に指定されたイベントハンドラを交互に実行します。2219行目は、クロージャ内から引数にアクセスするために値を保存しています。
2223行目は、this.lastToggleに0と1を交互に設定するための処理で、現在の値が0ならば1を、1ならば0が割り当てられます。これにより2229行目で引数に指定されたどちらの関数を実行されるかが決まります。2226行目は、ブラウザ標準のclickイベントを実行させないための予防処理になります。
2229行目は、実行時にならないとthisが分からないため、apply関数を利用して呼び出します。
jQuery.fn.hover()
jQuery.fn.hover()メソッドは、選択された要素に自身の'mouseenter'および'mouseleave'イベントを割り当てます。引数に指定されたfnOverおよびfnOutがそれぞれのイベントハンドラになります。
jQuery.fn.ready()
jQuery.fn.ready()メソッドは、jQueryライブラリの中でも重要な位置を占める$(document).ready()を定義している部分になります。Webアプリケーションの実行速度が決まる重要な部分であり、とても凝った作りになっています。
まず、2239行目にて、2284行目で定義されているbindReady()メソッドを実行します。bindReady()メソッドは、ブラウザごとの違いを吸収しつつDOMの準備ができたことを検知するためのめそっどになります。
2242行目は、DOMの準備ができているかどうかを確認し、準備できていれば即座に関数を実行します。もし、できていなければ、jQuery.readyListのキューに入れておいて、準備ができた時点で実行されるようにします。
jQuery.ready()
2256行目のisReadyは、DOMの準備ができているかどうかを表す変数で初期値はfalseです。
2257行目のreadyListには、準備ができた際に実行される関数が格納されます。
2259行目のjQuery.ready()メソッドは、2284行目で定義されているbindReady()メソッドから、DOMの準備ができた際に呼び出されます。2261行目のif文により、既にjQuery.isReadyがtrueであれば、何もしません。そうでなければ、jQuery.isReadyをtrueに設定し、jQuery.readyListに設定された関数を登録された順番に実行していきます。実行後、2273行目にてjQuery.readyListの中身を空にします。
最後に2277行目で、その他に設定されたreadyイベント実行するために、triggerHandler()メソッドを呼び出します。
bindReady()
bindReady()メソッドは、DOMの準備ができたかどうかを判定するための関数です。ブラウザごとに判定方法が異なるため、処理が分かれています。まず、2285行目ですが、readyBound == trueすなわち、一度でもこのbindReady()関数が実行されていたら何もせずに処理を戻します。そうでなければ、readyBoundにtrueを設定します。そして、次の行からがブラウザごとの判定処理になります。
2289~2291行目は、MozillaなどDOMContentLoadedイベントが利用可能な場合にDOMContentLoadedイベント発生時に先ほどのjQuery.ready()メソッドを実行するように設定します。
2295~2307行目は、Internet Explorerでフレーム内からの呼び出しでない場合です。IEの場合はDOMContentLoadedイベントがないので、document.documentElement.doScroll("left")が使えるようになるかどうかを繰り返し監視して、doScrollメソッドが使えるようになったら、jQuery.ready()メソッドを実行します。2302行目は、arguments.calleeつまりこの無名関数自身を繰り返し実行するための処理です。
2309~2319行目は、Operaの場合の処理です。addEventListenerによってDOMContentLoadedイベントを監視するのはMozillaの時と同様なのですが、2312行目のforループによってdocument.styleSheetsが利用可能になるまでウェイトする部分が異なります。これは、Operaの場合は、DOMContentLoadedイベントが発生した時点ではスタイルシートに関する処理が完了していない問題を回避するための処理です。
2321~2338行目は、Safariの場合の処理です。SafariもDOMContentLoadedをサポートしていないので、こちらは少々複雑です。まず、2325行目でdocument.readyStateがloadedまたはcompleteになるのをひたすら待ちます。次に2330行目で、jQuery("style, link[rel=stylesheet]").lengthを実行して、styleタグと読み込まれているCSSファイルの数をチェックします。そして、この数がdocument.styleSheets.lengthと一致するまで待ちます。つまり、Operaの時と同様にスタイルの読み込みが完了するのを判定しているわけです。そして、この2つが完了してようやく、2336行目でjQuery.ready()メソッドを実行します。
これまでのどのブラウザにも当てはまらなかった場合は、最後の手段として、2341行目にてwindow.onloadイベントを設定します。ここで疑問になるのは、どうして最初からwindow.onloadイベントを利用しないのかということですが、JavaScriptを使ったアプリケーションにとって、$(document).ready()の実行開始はアプリケーションの動作開始を意味します。この実行開始のタイミングをできるだけ早くすることで、ユーザの体感速度の向上が図れます。window.onloadだと、画像のロードも含めてページの読み込みが完全に完了したタイミングになるのですが、jQueryの動作に必要のはDOMおよびstyleの読み込みが完了したタイミングです。これを知るために、ブラウザごとに複雑な判別処理をしているわけです。
各種イベントメソッドの登録
2344行目からは、各種イベントメソッドをjQuery.fnオブジェクトに登録する部分になります。2344行目から列挙されているカンマで区切られたそれぞれのメソッドを登録します。2349~2350行目が登録するメソッドの実体で、引数が渡されてきたら引数に指定された関数をイベントハンドラとして割り当てます。引数が渡されてこなければ、trigger()を使ってそのメソッドを実行します。どちらが実行されるかは、実際にこれらのメソッドが呼び出された時点で決まります。
withinElement()
2356行目からは、イベントが他の要素内で起こっているかどうかをチェックするための関数です。2358行目のevent.relatedTargetは、mouseover/mouseoutのイベント発生時にマウスカーソルがどこから来たのか、もしくはどこへ行ったのかという値を取得します。そして、2360行目で親要素を再帰的に探して、2362行目でその親要素が第2引数と等しいかどうかを判定して返します。