JavaScriptで即時関数(function(){…})()を使う
JavaScriptには無名関数を定義・宣言すると同時に即時実行するための構文、即時関数というものがある。
この即時関数を利用することで、疑似的にブロックスコープを再現できる。
即時関数の構文
基本的には、以下のような構文となる。(他にも記述方法がいくつかある)
1 2 3 | (function () { //処理を記述 })(); |
また、以下のように引数を指定することもできる。
1 2 3 | (function (param1, param2, ...) { //処理を記述 })('foo', 'bar', ...); |
その他、通常の関数と同様に戻り値を持たせることも可能である。
※この場合、functionを丸括弧で括る必要はない。(理由は後述)
1 2 3 | var result = function (param1, param2) { return param1 + param2; }(3, 5); |
functionを丸括弧で括る目的
即時関数はfunctionを丸括弧で括ることで実現されているがこの目的は、「関数を式として評価させる為」である。
JavaScriptにはfunction文とfunction式があり、以下のようにfunctionから始まる関数がfunction文、 式の中で関数を定義したもの(つまりfunction以外で始まるもの)がfunction式となる。
1 2 3 4 5 6 7 | //function文 function func() { } //function式 var func = function () { }; |
因みにfunction文とfunction式では挙動が異なり、function文の場合は実行された際に関数が常に先頭に定義される。その為、function文はコード上のどの位置に存在しても対象の関数を呼び出すことができる。しかし、function式は読込まれた位置で関数が定義される為、記述位置よりも前で実行することはできない。
また、function式であれば、関数呼び出しの演算子”()”をつけて関数定義と同時に関数呼び出しができる。
つまり、即時関数として関数を定義する上でfunctionを丸括弧で括る目的は、関数をfunction文ではなく、function式として扱う為ということになる。
戻り値を変数に代入する即時関数の場合、functionを丸括弧で括る必要がないのも既にfunction式として評価されている為である。
因みにfunction文に関数呼び出しの演算子”()”をつけるとSyntax Errorとなる。
1 2 3 | function (){ console.log("foo"); }(); |
これはfunction文が”}”で終わったとみなされ、以下のようなイメージで解釈される為である。
1 2 3 4 | function (){ console.log("foo"); } (); // <-Syntax Error |
“();”は、JavaScriptとして不正な構文となる為、ここでエラーが発生する。
繰り返しになってしまうが、この挙動を回避する為にfunction式の記述にする必要があり、その方法としてfunctionを丸括弧で括る(function以外で始まる)ということに繋がる。
即時関数を利用する目的
即時関数を利用する目的は、スコープを汚染せずに新たなスコープを作成することにある。
JavaScriptのスコープは、「グローバルスコープ」と「関数スコープ(ローカルスコープ)」のみであり、関数は任意にスコープを作る手段と成り得る。
関数スコープの中でvarを使って定義された変数は関数の中でローカルな変数になるので、関数の外側の変数を上書きしない。
再利用されることがない一連の処理の中で一時変数を使用する際、それらの変数をすべてグローバル変数とするとグローバルスコープに不要な変数が積み上がっていき、変数名のバッティング等の予期せぬトラブルが発生する可能性がある。
一連の処理を即時関数で包み一時的なスコープの中で実行することで、プログラムをスコープ上のサンドボックス内で実行することになり、スコープの外側への影響を防ぐことができる。
また、即時関数を利用することで関数そのものを格納する変数も必要がなくなる為、グローバルスコープを全く汚染せずに一時変数を関数スコープ内に包み込むことができる。
その他、一連の処理の結果が不変で結果のみが必要な場合、その結果を得るために何度も同じ処理を実行する必要がなくなるメリットもある。
即時関数の利用イメージ
<初期化処理への利用>
ページ読み込み時の初期化処理(一度限りの実行)に利用する。
以下の例は、”label_date”ラベルを用意してページ読み込みと同時に日時を表示している。
一時変数である”label”、”now”、そして処理そのものは初期化終了後には不要となるので、即時関数で包みグローバルスコープの汚染を回避できる。
1 2 3 4 5 6 7 8 9 10 11 | document.addEventListener('DOMContentLoaded', function() { //初期化処理 (function () { //ラベルオブジェクト取得 var label = document.getElementById('label_date'); //現在日時を取得 var now = new Date(); //ラベルに日時を設定 label.textContent = now; }()); }); |
以下の例は、ユーザーエージェントの判定を行っている。
判定結果のみが必要であり、判定処理を即時関数とすることで、その結果を取得する為に何度も判定処理を行う必要がなくなる。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | var device = function(u) { return { Tablet: (u.indexOf("windows") != -1 && u.indexOf("touch") != -1 && u.indexOf("tablet pc") == -1) || u.indexOf("ipad") != -1 || (u.indexOf("android") != -1 && u.indexOf("mobile") == -1) || (u.indexOf("firefox") != -1 && u.indexOf("tablet") != -1) || u.indexOf("kindle") != -1 || u.indexOf("silk") != -1 || u.indexOf("playbook") != -1, Mobile: (u.indexOf("windows") != -1 && u.indexOf("phone") != -1) || u.indexOf("iphone") != -1 || u.indexOf("ipod") != -1 || (u.indexOf("android") != -1 && u.indexOf("mobile") != -1) || (u.indexOf("firefox") != -1 && u.indexOf("mobile") != -1) || u.indexOf("blackberry") != -1 }; }(window.navigator.userAgent.toLowerCase()); console.log(device); |
<プライベートプロパティ/メソッドの定義への利用>
JavaScriptには、privateやprotected等のアクセス修飾子が無い為、オブジェクト内に定義されたプロパティ/メソッドは、全てパブリックなものとして扱われる。
しかし、即時関数とクロージャを利用することで、疑似的にプライベートなプロパティ/メソッドを実現することができる。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | var counter = function() { //プライベートメンバ var count = 0; //countをクロージャで補足したincrementとshowを返す return { //加算メソッド increment: function() { count++; }, //ログ出力メソッド show: function() { console.log(count); } }; }(); //メソッドの実行 counter.show(); //0が出力される counter.increment(); counter.increment(); counter.show(); // 2が出力される console.log(counter.count); // undefinedが出力される |
即時関数のいろいろな記述方法
即時関数は、function式として認識させられる構文であれば記述することができる。
つまり、単項演算子から始まる関数を記述すればよい。
単項演算子には”+”、”-“、”!”、”void”、”typeof”等があり、これらも即時関数の記述に利用できる。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | (function(){ console.log("another parentheses"); }()); +function(){ console.log("plus"); }(); -function(){ console.log("minus"); }(); !function(){ console.log("exclamation"); }(); void function(){ console.log("void"); }(); typeof function(){ console.log("typeof"); }(); |
上記は、全て動作する。
但し、丸括弧を利用した場合は戻り値に変化がないが、”+”、”-“の場合は戻り値がnumber型にキャストされ、”!”の場合は戻り値がbool型にキャストされ、”void”の場合は戻り値が常に”undefined”となる。
因みに”new”も単項演算子であるが、スコープ内でのthisの示すものが変わってくるので即時関数を記述する目的での使用は控えた方が良いかと思われる。