Real Blog

レジェンド鈴木が日々感じたことを哲学するブログ。書評、エッセイ、ポエムも書いてます。

jQueryがなぜ動くのか

jQueryはHTMLのDOMを扱うのにとても便利なライブラリですが、
実際にどのような形で動作しているかを理解せずに使っている人も多いと思います。

もちろん理解せずに使ってもいいと思いますが、理解できるとjQueryを独自の拡張をしたいときや、プラグイン等で何かしらの問題が発生したときに対応しやすくなると思います。

そこで今回はjQueryの機能でよく使うshowメソッドが実行されるまでの流れを追って、jQueryがどのような流れで動いているかを解説していきたいと思います。



jquery.png

jQueryの定義

まずjQuery自体がどのように定義されているかを見ていきましょう。
今回は「jquery-1.7.1.js」のコードを観察対象とします。
jQueryオブジェクトは即時関数内のスコープで定義されています。
jQueryオブジェクトにはeach,isArray,readyなどののユーティリティーな機能が定義されていて、 jQuery.fn にshow,hideなどの振る舞いが定義されていて、jQuery.prototype に代入されています。

//即時関数内スコープ
(function( window, undefined ) {

//22行目付近 jQueryを定義
var jQuery = (function() {

//97行目付近 jQueryのプロトタイプを定義して、jQuery.fnに代入
jQuery.fn = jQuery.prototype = {

//9251行目付近 グローバルオブジェクトwindowのjQuery, $ に即時関数内で定義したjQueryを代入
window.jQuery = window.$ = jQuery;

上記のコードの説明で97行目付近から9251行目付近まで一気に飛んでいますが、
この間ではjQuery, jQuery.fnにいろいろな機能を定義しています。



showメソッド実行時の動作の流れ

今回は下記のように定義したhtmlエレメント#hogeに対して $('#hoge').show() を実行した際の流れを追っていきます。
<div id='hoge' style='display:none;'></div>
$ = jQueryのコンストラクタ関数は以下のように定義されています。
var jQuery = function( selector, context ) {
		// The jQuery object is actually just the init constructor 'enhanced'
		return new jQuery.fn.init( selector, context, rootjQuery );
	}
jQuery.fn.initではセレクターが処理されて、対象のDOMオブジェクトのリストがjQuery.fn.initのプロトタイプに設定されます。
今回のように#でIDを指定した場合は下記の処理が行われます。
elem = document.getElementById( match[2] ); //match[2]には'hoge'が入っている。
// Check parentNode to catch when Blackberry 4.6 returns
// nodes that are no longer in the document #6963
if ( elem && elem.parentNode ) {
	// Handle the case where IE and Opera return items
	// by name instead of ID
	if ( elem.id !== match[2] ) {
		return rootjQuery.find( selector );
	}

	// Otherwise, we inject the element directly into the jQuery object
	this.length = 1;
	this[0] = elem;
}

this.context = document;
this.selector = selector;
return this;

このようにして、プロパティ0にHTMLエレメント#hogeが代入されたjQueryオブジェクトが生成されます。

次にshowメソッドですが、8234行目付近に定義されています。
showメソッドでは、jQueryオブジェクトの各htmlエレメントのstyle.display属性に対して、
デフォルトの状態(空文字)であれば、そのままで、
noneが指定されていて非表示状態であれば、htmlタグに応じたデフォルトの値を設定しています。
今回#hogeはdivタグで、cssで display:none と指定していたので、displayはdivタグのデフォルト値である、blockが設定されます。
show: function( speed, easing, callback ) {
	var elem, display;

	if ( speed || speed === 0 ) {
		return this.animate( genFx("show", 3), speed, easing, callback );

	} else {
		for ( var i = 0, j = this.length; i < j; i++ ) {
			elem = this[ i ];

			if ( elem.style ) {
				display = elem.style.display;

				// Reset the inline display of this element to learn if it is
				// being hidden by cascaded rules or not
				if ( !jQuery._data(elem, "olddisplay") && display === "none" ) {
					display = elem.style.display = "";
				}

				// Set elements which have been overridden with display: none
				// in a stylesheet to whatever the default browser style is
				// for such an element
				if ( display === "" && jQuery.css(elem, "display") === "none" ) {
					jQuery._data( elem, "olddisplay", defaultDisplay(elem.nodeName) );
				}
			}
		}

		// Set the display of most of the elements in a second loop
		// to avoid the constant reflow
		for ( i = 0; i < j; i++ ) {
			elem = this[ i ];
			if ( elem.style ) {
				display = elem.style.display;
				if ( display === "" || display === "none" ) {
					elem.style.display = jQuery._data( elem, "olddisplay" ) || "";
				}
			}
		}

		return this;
	}
}
このようにして、非表示だった#hogeがdisplayにblockが代入されることで、表示状態になりました。

jQueryは色々な機能が搭載されている大きなライブラリですが、今回のように1つ1つ見ていけばシンプルな構成になっているというのが解ると思います。