即時関数(function(){ ... })()の別の書き方いろいろ

Posted by Hiraku on 2011-04-23

JavaScriptでよく使われるコード片に即時関数というものがあります。無名関数を宣言して即実行することで、ブロックスコープの存在しないJavaScriptにおいて擬似的にブロックスコープを再現します。

var a = "global";

(function(){
  var a = "local";
  alert(a); //local
})();

alert(a); //global

一番有名なのはこの(function(){ ... })()の形式なのですが、なぜfunctionの外側にカッコが必要なのか不思議に思ったことはないでしょうか? ためしにfunction(){ ... }()と書いてみると、Syntax Errorが発生します。

なぜfunction(){ ... }()はSyntax Errorなのか

JavaScriptにはfunction文function式があって、微妙に挙動が違います。乱暴に書いてしまうと、"function"というキーワードから始まるように書くと文になり、それ以外の書き方をすると式になります。

例えば下のような書き方のとき、セミコロンを書かなくても動きますよね? これはJavaScriptの処理系が、function文であると認識して、「}」の直後で強制的に文を区切るからです。直後にカッコがあっても連結されません。

function hoge() {
  alert("hoge");
}               // ここにセミコロンを書く必要はない!
(a == b) ? alert('a==b') : alert('a!=b') ;

これは無名関数でも同じなので、function(){ ... }()と書くと、処理系の内部では以下のように解釈されてしまいます。このとき、();はJavaScriptとして不正な構文なので、ここでエラーが発生するのです。

function(){ ... };
();             // <-Syntax Error!

この挙動を打ち消すにはfunction式として書く必要があります。即ちfunction以外から書き始めればいいので、このためにカッコを使っているわけです。

カッコ以外の書き方を考える

逆に考えると、function式として認識させられる構文であれば、別にカッコを使わなくても即時関数が書けることになります。ここで使えるもの、それは単項演算子です。

JavaScript Gardenに単項加算演算子「+」を使った例が出ていますが、JavaScriptの単項演算子と言えば「+」「-」「!」「void」「typeof」がありますので、これらも使うことができます。

var a = "global";

(function(){
  var a = "another paren";
  alert(a); //"another paren"
}());

+function(){
  var a = "plus";
  alert(a); //plus
}();

-function(){
  var a = "minus";
  alert(a); //minus
}();

!function(){
  var a = "ex";
  alert(a); //ex
}();

void function(){
  var a = "void";
  alert(a); //void
}();

typeof function(){
  var a = "typeof";
  alert(a); //typeof
}();

これ、なんと全部動きますw 面白いですよね。「+」は「()」に比べて1byte省略できるので、短く書きたいときに使える…かな?

newは若干動作が違うので注意が必要

関数限定で使える演算子である「new」も単項演算子の一種ですが、スコープ内でthisの示すものが変わってくるので少し注意が必要です。また、オブジェクトの初期化が発生するために、その他の書き方に比べれば少しパフォーマンスが劣ります。(何万回もループしない限り体感できないレベルでしょうけど)

newには引数を使わない場合はカッコを省略できるという面白い性質があります。これを応用するとnew function(){ ... }という書き方が生まれます。若干読みやすいかもしれません。

new function(){
  var a = "new";
  alert(a); //new
};

所感

  • voidが意外と読みやすい。
    • 意味的・文脈的に自然。
    • voidは使いどころがほとんどないので、即時関数に使っていればソースコードの検索がしやすくなる
  • 数年勉強しても、まだ言語的な発見が見つかるのがJavaScript。

※「即時関数」という名前はJavaScriptパターンで覚えました。
JavaScriptパターン ―優れたアプリケーションのための作法 [大型本] / Stoyan Stefanov (著); 豊福 剛 (翻訳); オライリージャパン (刊)


念のため追記

原理さえわかってしまえば他にもいくらでも書き方があることがわかります。配列コンストラクタの[]を使って[function(){…}()]としてもいいですし、代入する場合はそもそも特別な書き方は必要なく、var hoge = function(){…}();とそのまま書くことができます。

ただ、書き方によっては戻り値が変わってしまうことは覚えておかなければなりません。

カッコを用いた場合は戻り値に変化がありませんが、「+」「-」は戻り値をnumber型にキャストし、「!」はbool型にキャストし、「void」に至っては常に「undefined」を返すようになります。

res = (function(){return "1"})(); //stringの"1"(元のまま)
res = +function(){return "1"}(); //numberの1
res = -function(){return "1"}(); //numberの-1
res = !function(){return "1"}(); //false
res = void function(){return "1"}(); //undefined

つらつら書いてきましたが結局のところ、(functiton(){…})()が最も汎用性が高く、パフォーマンスにも優れ、そこそこの可読性も保つ最大公約数だと言えるんじゃないでしょうか。(戻ってきてしまいました…)

誰だか知りませんが、最初にこの記法を思いついた人は偉大です。



keyword: javascript

JavaScriptの最新記事