jQueryで$(document).readyを複数実行した場合のthisやvarについて

はじめに

jQueryを使う時は、$(document).ready(handler)やその省略法である$(handler)でdom構築ができてからコードを実行することが多い。(handlerは無名関数などの関数オブジェクト)

で、たまに複数のファイルで何度も$(handler)を呼び出すことがあるのだけれど、その際$()の引数に渡しているhandler内のthisやvar宣言について混乱しがちだったので整理しておく。ちなみに検証環境はChrome ver24、jQuery1.9.1。概念はES3を基準にする。

前提

書き方色々

以下の3つは同義(真ん中は推奨されない)

$(document).ready(handler)
$().ready(handler) //this is not recommended
$(handler)

http://api.jquery.com/ready/

複数呼び出した場合

呼び出した順に全てが実行される。別々のファイルにしていても同じ。

$(function() {console.log('hoge');});
$(function() {console.log('fuge');});
$(function() {console.log('piyo');});

//結果:
//hoge
//fuge
//piyo

jQueryのtutorial

これをふまえて、handler内のthisやvarの挙動をみていく

this

handler内のthisはdocumentオブジェクトになる。なので、例えば最初のhandler内で「this.x」とすれば、後で呼び出したhandler内でも「document.x(もしくはthis.x)」で参照することができる

$(function() {
  this.x = 'xxx';
  console.log(this === document); //true
});

$(function() {
  console.log(document.x); //xxx
  console.log(this.x); //xxx
});

jQuery1.9.1のソースコードを見てみると、以下の部分でhandlerのthisにdocumentオブジェクトをセットしていることがわかる

readyList.resolveWith( document, [ jQuery ] );

(略)

// 最終的には以下の部分でapplyの第1引数にdocumentオブジェクトがセットされる
// これにより、handlerのthisがdocumentとなる
list[ firingIndex ].apply( data[ 0 ], data[ 1 ] )

varあり

関数コード内でvar 宣言した変数は、アクティベーションオブジェクトのプロパティになる。なので、例えば以下のようにすると、後で呼び出したhandlerからxにはアクセスできない(実行コンテキストが変わってしまうから)

$(function() {
  var x = 'xxx'; //xはアクティベーションオブジェクトのプロパティ
});

$(function() {
  console.log(x); //error
});

アクティベーションオブジェクトについては、こちらが詳しい
http://alpha.mixi.co.jp/2011/10763/#ecma-263-3-2-variable-object-in-function-context

varなし

handler内でvar宣言しなかった変数は、globalオブジェクトのプロパティになる。これはスコープチェーンを遡ってグローバルオブジェクトにたどり着くため(逆に、スコープチェーンの途中でvar xと宣言されていれば、そこで解決する)。なので、以下のようにすると後で呼び出したhandlerでもxを参照できる。

$(function() {
  x = 'xxx'; //xはglobalオブジェクトのプロパティ(スコープチェーンを遡るので)
});

$(function() {
  console.log(x); //xxx
  console.log(window.x); //xxx
});

http://stackoverflow.com/questions/1470488/difference-between-using-var-and-not-using-var-in-javascript

複数ファイルで$(handler)を使いつつ、変数を共有

handler内で定義した変数を他のファイルのhandler内と共有するにはどうすればよいか?
色々なやり方があるけれど、グローバル空間をあまり汚さない方がバグを生みにくいと思う。
なので、自分の場合はプロジェクト固有の名前空間となるオブジェクトを作ってグローバルオブジェクトにセットし、
外部に公開したいAPIはそのプロジェクト固有の名前空間のプロパティとしてセットするようなやり方を主に使おうと思う

html

<html>                                                                                                                                                                                                      
<head>
  <script src="http://code.jquery.com/jquery-1.9.1.min.js"></script>
  <script src="a.js"></script>
  <script src="b.js"></script>
</head>
</html>

a.js

$(function() {                                                                                                                                                                                              
  var x = 'xxx';

  App = {
    foo: function() {console.log(x);}
  };
});

b.js

$(function() {                                                                                                                                                                                              
  App.foo();
});