読者です 読者をやめる 読者になる 読者になる

Programming log - Shindo200

イベント参加記録とプログラミング系の雑記

JavaScript の関数の中で window と undefined を定義する理由を調べてみた

jQuery などの JavaScript のライブラリのソースコードを読んでいると、このようなコーディングパターンをよく見かけます。

(function(window, undefined){
  // ...
})(this);

これはすぐに実行される関数の中にライブラリの実装コードを閉じ込めて、関数の中で定義した変数がどこからでも参照できる変数(グローバル変数)になる問題を回避しています。これは「グローバル汚染の回避」と呼ばれています。下記のコードを見てください。

(function(window, undefined){
  var foo = "foo";
  console.log(foo) //=> foo
})(this);

console.log(foo) //=> undefined

トップレベルから関数の中で定義した変数を参照できていませんので、たしかにグローバル汚染の回避ができています。ここまでは大丈夫でしょうか。

さて、ここからが今回の記事の本題に入るのですが、先ほどのコードは関数の引数で変数 window と変数 undefined を定義しています。JavaScript は関数の中で使用する変数(ローカル変数)が定義されていないときは、グローバルオブジェクトを参照してきますので、ローカル変数 window とローカル変数 undefined は定義しなくても良いはずなのですが、何故こんなことをしているのでしょうか。

window

window はブラウザに表示されているウィンドウの情報を持ったグローバルオブジェクトです。コードのトップレベルから this を使って参照することができます。

JavaScript では「グローバルオブジェクトを参照するより、ローカル変数を取得するほうが速度は早い」という特徴を持っています。なので、グローバルオブジェクトの window を参照するより、あらかじめローカル変数に window を代入しておいて、それを取得したほうが速度は早いようです。実際にそれぞれのコードの速度を測定してみます。jsperf.com という JavaScript のパフォーマンス速度計測アプリに今回比較したいコードを書いてみました。
http://jsperf.com/copy-window

<script>
  (function() {
   window.a = function() {
    window.aa = 1;
   };
  })(window);
  
  (function(window) {
   window.b = function() {
    window.bb = 1;
   };
  })(window);
</script>
// ----- Test code -----
// Dont't copy window
a();
// copy window
b();

「Don't copy window」がグローバルオブジェクトから参照する方法、「copy window」がローカル変数から参照する方法になります。パフォーマンステストを実行してみると、「copy window」のが早く処理が終わっていることがわかります。

f:id:Shindo_Masaya:20131129113140p:plain:w400

undefined

ECMAScript3 の仕様からグローバルオブジェクトの undefined プロパティについての記述を引用します。
http://www.ecma-international.org/publications/files/ECMA-ST-ARCH/ECMA-262,%203rd%20edition,%20December%201999.pdf

15.1.1 Value Properties of the Global Object

15.1.1.3 undefined

The initial value of undefined is undefined (8.1). This property has the attributes { DontEnum,
DontDelete}.

undefined プロパティは、値が代入されていないことを意味する undefined 値を返します。しかし、値の変更制限はかけていないので、この仕様を標準にしているブラウザでは undefined プロパティの値を変更することができました。

// InternetExplorer8 で実行
undefined = "foo";
console.log(undefined); //=> foo

関数の中での undefined が undefined 値を返すことを保証するためには、undefined 値を代入したローカル変数 undefined を定義しておく必要があります。そこで、下記のようなコーディングをすることがあります。

(function(undefined){
  // ...
})();

下記のコードの実行結果を見ると、関数の中での undefined が undefined 値を返していることがわかります。

// InternetExplorer8 で実行
undefined = "foo";
console.log(undefined); //=> foo

(function(undefined){
  console.log(undefined); //=> undeinfed
})();

ただし、これは ECMAScript3 の仕様での話です。ECMAScript5 の仕様からグローバル変数 undefined についての記述を引用します。
http://www.ecma-international.org/publications/files/ECMA-ST-ARCH/ECMA-262%205th%20edition%20December%202009.pdf

15.1.1 Value Properties of the Global Object

15.1.1.3 undefined

The value of undefined is undefined (see 8.1). This property has the attributes { Writable: false, Enumerable: false, Configurable: false }.

ECMAScript5 の仕様では undefined プロパティの Writable が flase となっていますので、値を変更することができなくなりました。

// GoogleChrome v31.0, FireFox v21.0, InternetExplorer9 で実行
undefined = "foo";
console.log(undefined); //=> undefined

たしかに undefined プロパティの値を変更することができなくなっています。そうなると、現在はローカル変数 undefined を定義することに意味が無くなったように感じます。今回の記事では各ブラウザのシェア数まで触れませんが、古いブラウザで閲覧されることを考えて、あえて定義してあるのではないでしょうか。

…と無理矢理まとめようとしたけど、全然すっきりしないですね。でも、時間ぎれなので、とりあずここまでにします。

2014/05/02 追記

知らない間にはてなブックマークが大量に付いていて怖いのですが、コメント確認しました。
http://b.hatena.ne.jp/entry/shindolog.hatenablog.com/entry/2013/11/29/113411

「今の JQuery では、この書き方は廃止されているので、積極的にやる必要はないですよ」

http://code.jquery.com/jquery-2.1.0.js

(function( global, factory ) {
  #...
// Pass this if window is not defined yet
}(typeof window !== "undefined" ? window : this, function( window, noGlobal ) {
#...
};

JQuery 2.0.0 ではまだ windowとか undefined が書いてありましたが、JQuery 2.1.0 では書いてありませんね。

「window ではなく、global という名前に変えて「ブラウザ以外の環境との互換をとる」という目的で使うのが良いのではないでしょうか」

TODO: global の使い方を調べて、あとで書き直します。

「この文章が何を言っているのかわからない」

関数の中での undefined が undefined 値を返すことを保証するためには、undefined 値を代入したローカル変数 undefined を定義しておく必要があります

TODO: 頭悪い感じ……あとで丁寧に書き直します。