jquery.jsを読み解く

第10回 jQueryライブラリ(2183行目~2364行目)

この記事を読むのに必要な時間:およそ 6 分

bindReady()

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: 

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.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(fn){
2350:     return fn ? this.bind(name, fn) : this.trigger(name);
2351:   };
2352: });
2353: 

2344行目からは,各種イベントメソッドをjQuery.fnオブジェクトに登録する部分になります。2344行目から列挙されているカンマで区切られたそれぞれのメソッドを登録します。2349~2350行目が登録するメソッドの実体で,引数が渡されてきたら引数に指定された関数をイベントハンドラとして割り当てます。引数が渡されてこなければ,trigger()を使ってそのメソッドを実行します。どちらが実行されるかは,実際にこれらのメソッドが呼び出された時点で決まります。

withinElement()

2354: // Checks if an event happened on an element within another element
2355: // Used in jQuery.event.special.mouseenter and mouseleave handlers
2356: var withinElement = function(event, elem) {
2357:   // Check if mouse(over|out) are still within the same parent element
2358:   var parent = event.relatedTarget;
2359:   // Traverse up the tree
2360:   while ( parent && parent != elem ) try { parent = parent.parentNode; } catch(error) { parent = elem; }
2361:   // Return true if we actually just moused on to a sub-element
2362:   return parent == elem;
2363: };
2364: 

2356行目からは,イベントが他の要素内で起こっているかどうかをチェックするための関数です。2358行目のevent.relatedTargetは,mouseover/mouseoutのイベント発生時にマウスカーソルがどこから来たのか,もしくはどこへ行ったのかという値を取得します。そして,2360行目で親要素を再帰的に探して,2362行目でその親要素が第2引数と等しいかどうかを判定して返します。

著者プロフィール

山下英孝(やましたひでたか)

大学を卒業後,大手SIerに就職し,電機メーカーの研究所勤務を経て,ウノウに入社。1年半に渡ってWebサイトの開発,ディレクション,運用を経験した後に独立。2008年2月よりフリーエンジニアとして活動中。好きな言語はJavaScriptとPythonで,Linuxサーバ運用管理も得意。

ブログWeboo! Returns.