[jQuery] DOMの構築待ちについて

先日、社内のWebデザイナーに標題についての解説をしました。自分の説明が悪くイマイチ理解してもらえなかった感がありましたが、JS(jQuery)を扱う上で重要かつ基礎的な知識の一つだと思いますし、改めて内容を整理しつつ記事にしてみました。

HTMLをほぼほぼ理解しているJS/jQuery初級者で、細かい用語などは自力で調べられる方が対象となります。


まず、JSからDOMを操作する時、当然ながらその対象はJSからアクセス可能な状態でなければなりません。

~
<script src="js/jquery-1.8.3.min.js"></script>
<script>
	alert( $( '#hoge' ).text() );
</script>
</head>

<body>
	<p id="hoge">Hello world!</p>
~

上記の4行目は、jQueryを使ってid:hogeを持つ要素の中にあるテキストを取得してブラウザのアラートで表示するスクリプトです。しかしこの例の場合、表示される値は「Hello world!」ではなく空文字列です。

ブラウザはページの上から下に向かってDOMツリーの構築(ページ内容の解析とレンダリング)を行いますが、その処理の中でscript要素を発見した時、その時点でその内容の構文解析と実行を行います。

なので上の例で空文字列が返るのは、スクリプトを実行する時点でまだ対称要素の解析が終了しておらず、JS(jQuery)からid:hogeを持つ要素を見つけられないためとなります。

従って、冒頭の通りですが上記4行目で「Hello world!」を表示させるためには、まずDOMツリーの構築を待たねばなりません。ブラウザによってDOM構築完了と見なす条件に差異があるため、ピュアJSでそれをハンドリングしようとすると少し骨が折れますが、jQueryでは以下のような簡単な構文で実現できます。

例 .1

<script>

	$( document ).ready( function(){
		// DOM構築完了後の処理
	} );

</script>

jQueryはDOM構築完了のイベントを内部に持っており、上記の記述によってそのハンドラをセットできます。ハンドラとして登録された関数はそのイベントが発火した(jQueryがDOM構築完了とみなした)時点で呼び出されるため、つまりその関数の中ではページ内にある全ての要素にアクセス可能であることが保証されます。

そして上記「例 .1」は、以下のように簡潔に記述することもできます。

例 .2

<script>

	$( function(){
		// DOM構築完了後の処理
	} );

</script>

$()はその引数として関数が渡された場合、それをDOM構築完了のハンドラであると見なし「例 .1」の処理を内部で実行します。そのため「例 .1」と「例 .2」は(オーバーヘッドの差を除いては)全く同じ結果を生むことになります。

また、jQuery v1.1からはDOM構築完了イベントのハンドラに対して引数にjQueryの参照が渡されるようになったので、それを利用したよりスマートな記述として、jQueryに比較的詳しいブログ記事などでは、以下のようなパターンも見かけます。

例 .3

<script>

	jQuery( function( $ ){
		// DOM構築完了後の処理
	} );

</script>

この記述には、これまでの二つの例と比べて「ハンドラ内で安全に$が使用できる」「$の識別子解決が最速で完了する」といったような利点があります。

まずは「ハンドラ内で安全に$が使用できる」について。
名前のユニークさという意味で、jQueryという変数名が意図せず置き換えられることはまず無いと言えます。しかし$に関してはどうでしょうか。prototype.jsなどでもグローバル変数の名前として$が使用されていますし、自分のあずかり知らぬところで名前が衝突する可能性を否定できません。

通常、jQueryと$は同じものを指しており、そして「例 .1」や「例 .2」もそれを前提にした記述です。大抵の場合はそれで問題ありませんが、万一、上記の例にあるような$の衝突が起こった場合、恐らくJSはエラーを投げて終了してしまうでしょう。

「例 .3」において、ハンドラ内で使われる$はグローバル変数の$ではなく、引数に取ったローカルな$(jQueryの参照)になります。そしてグローバル環境では$ではなくjQueryを使用しているため、グローバルの$が何か別のスクリプトによって置き換えられていようが、このスクリプトは問題無く作動します。(逆にjQueryの$が他のライブラリの$を置き換えないようにするためにはjQuery.noConfrict()などを利用するといいでしょう)

次に「$の識別子解決が最速で完了する」ですが、JavaScriptは処理の実行を行う際に、まずそこで使われる変数名(識別子)がどこで定義されているのか検索します。そしてそれは実行コンテキストからグローバル環境までスコープチェーンを順に遡りながら行われ、発見した時点でその値が使用されます。最後に検索されるグローバル環境でも見付からなかった場合、その識別子はundefined(未定義)として扱われます。この検索のことを称して識別子(変数名)の解決などと呼ばれます。

「例 .1」と「例 .2」のハンドラ内に記述される$はグローバル変数としての$であるため、その識別子を解決するにはグローバル環境まで遡って検索しなければなりません。しかし「例 .3」のハンドラ内の$はハンドラが引数にとったjQueryの参照でありこれは実行コンテキストにおいてローカル変数とほぼ等しく扱われるので、識別子の解決は最速で完了します。

余談ですが、jQueryプラグインの定義などでよく使われている以下の記述も上記と同じく、$の安全性と高速なアクセスを考慮したものです。(それだけを意味するものではありませんが)

( function( $ ){
	// プラグインの定義
} )( jQuery );

以上、jQueryにおけるDOM構築待ちについての話でしたが、まあ$の安全性はともかくとして、高速な識別子解決に関しては通常のWebサイト制作において全く気にする必要が無いくらい微々たる差しかありません。一応意味がありますってのと、無いよりは良いかもってことで…(‘A’;)

以前勤めていた会社で、「丸暗記は応用が利かないのでまるで意味が無い」といったような話を聞いた事がありました。まったく同意でかつ耳が痛いお話なのですが、これもそういう事なのかも知れません。

偉そうに長々と書きましたが、参考になりましたら幸いです。おわり。


コメントを残す

メールアドレスが公開されることはありません。


五 × 4 =