即時関数の頭の';'ってなに?なんで最初に'!'付けているの?そんな話から色々盛り上がったのでメモというか備忘録というかそんな感じです。
Grouping Operator
まずは即時関数の動作について理解しておく必要があります。
javascriptは全てがステートメント(文)に成っていなければならない
javascriptは全てを通してステートメントとして解釈される必要があります。つまり、基本的に裸の式があってはならないということになります。
function(){};
例としてこれがNGになります。要するに「頭からfunction
で始まる式を書いてはいけない」ということです。
ただ、例外としては関数宣言がありますね。
function name(){};
関数宣言に関しては評価してくれるので、少しややこしいところもあります。実のところは、式として二通りの解釈が出来てしまうのですが、ここはとりあえず難しいことは抜きにして、まずは無名関数は評価出来ないとしておくと考えましょう。
そして、それを回避する方法のひとつがGrouping Operatorといった手法です。
即時関数
以下はよく見る即時関数です。関数をグルーピングしてあげることで式として認識させて、実行可能としているわけですね。
(function(){
//implement
})();
つまりは
//このfunction文(ステートメント)をグループ化、それを仮にグループ'A'とするならば
(function(){});
//最後に()を付けることで
A();
//と同じ事になります。
みたいな認識ですね。上記の動作を文として成立させるなら
A = function(){};
A();
更に省略すれば
A=function(){}();
でもOKということです。
’;’を付ける理由
言ってしまえばグルーピングを確定させるために付けるものです。例えば開発の分散など、何かしらの影響で以下の様な文になってしまった場合です。
a
(function(){});
javascriptはこの式をどう評価するでしょうか。
a(function(){});
こうなっちゃいますね。つまりa()という関数の定義を探しに言ってしまいます。もちろんエラーになるか、もしaが宣言されていたらよくわからないことになっちゃいますね。これを避けるために頭に’;’をつけておきます。
’!’を付ける理由
基本的には’;’を付ける理由と同じです。ただ!に関してはグルーピングをする必要がありません。それをすることにより「function
で始まってないけない」というルールを回避できるわけですね。
即時関数であれば
!function(){}();
と書くことが出来ます。無駄な括弧をはずし、安全にfunction
を定義することが出来ますね。
’!’でなくても大丈夫!
実は'!'以外にも使える文字は多く、ステートメントとして成立し、頭がfunction
でなければなんでもいけます。これをExpressionStatementといいます。ExpressionStatementは例外を除き({
またはfunction
で始まらないこと)、記述可能な式があるというルールです。1+1
などという式はそのまま書いてもステートメントとして判断し、評価可能となるわけです。
いろんな宣言ができますね。
+function(){}();
-function(){}();
[function(){}()];
a = function(){}();
0||function(){}();
0&&function(){}();
true,function(){}();
false,function(){}();
0 != function(){}();
0 === function(){}();
もう後半とかよくわかりませんが、まだまだあります。そして全て即時関数として実行可能です。
ここで関数宣言がなぜ評価できるのかがわかる
ここで、最初にも書いた関数宣言がなぜ評価されるのか、が理解することが出来ます。文法として例外の記法に含まれていますが、それこそが「ExpressionStatementが何故あるのか」の理由になっています。
関数宣言の場合は、体感上では以下のように関数の定義と同じような評価がされると思います(実際には違います)。
function name(){};
//と
name = function name(){};
//は意味合い的には同じ型。
name();
両者ともコールの仕方に関しても同じです。しかしその似ているようで違うそれこそが、同じく最初に書いた「二通りの解釈」といった箇所の鍵となります。その二通りとは、名前付き関数と関数宣言です。
function name(){};
/* name = */ function name(){};
関数を定義する際に、式とされたfunction
に名前をつけるのが関数名です。それらが頭からfunction
で始まっている(つまり定義先変数name
がない状態の式)Expression(式)である場合とした時に、エラーを返す仕様なのです。もし、ExpressionStatement がなければ、javascriptがこの文を評価する時、どちらで評価していいのかわからなくなってしまうわけです。つまりExpressionStatementによって、後者を例外として弾くことにより、関数宣言をステートメントとして成立させているわけですね。
文法は一緒なのに意味が違う。なかなか混乱しますが、ExpressionStatementのルールによって関数宣言が成立しているということです。これが「頭からfunction
の式を書いてはいけないルールの実態」です。
たまに即時関数とした無名関数と、関数宣言や関数の定義が対になって説明されてたりしますが、実態は違います。視認上は評価されているというだけで、関数宣言はステートメントです。そして、即時関数は関数式をステートメント化する手法です。無名関数の対は名前付き関数であり、どちらもExpressionであるかぎり、ステートメントとしなければ実行できないものです。そして、グルーピングなどにより、どちらも即時関数に成り得ます。
//即時関数(無名関数)
(function(){})();
//即時関数(名前付き関数)
(function name(){})();
名前付き関数にした時の利点といえば、arguments.calleeを使わずとも回帰利用が可能なところですね。そして、関数宣言とは違いスコープ外からの参照は出来ない所も理解する必要があるでしょう。
!function name() {
console.log(name);
}();
/*responce
function name() {
console.log(name);
}
*/
name();
//undefined ※外部からの利用は不可
function name() {
console.log(name);
}
//undefined ※この時点で`name`は定義されていない。
name();
/*responce
function name() {
console.log(name);
}
※実行するには呼び出す必要がある。外部から利用が可能。
*/
ついでに’{’の事も
{
についても同じ理由です。
//ラベル
!function(){ name : for(var i = 0; i<100; i++){ break name; } }();
//オブジェクト
A={ name : 'value' };
例えばこの様に、文法として用途が二通りあるため、ただ宣言された場合は、何かのラベルなのかオブジェクトなのか判断がつかなくなってしまいます。そのために制限を設けて多重評価をしないようにできているわけです。
ちなみに単一ではオブジェクトとして評価されるので、グループ化すれば
;({ init : function(e){console.log(e)} }).init('hoge');
//hoge
;({ hoge : 'hoge' }).hoge;
//hoge
;({ init:function(p, e){
p.appendChild(this.create(e + ' project'));
},
create:function(e){
var div = document.createElement('div');
div.textContent = e;
return div;
}
}).init(document.getElementById('main_wrapper'),'new');
利用的に価値が有るのかは別として、こんな使い方もできます。
まとめ
javascriptは一歩奥へ踏み入れると、思った以上にルールがあり、複雑な仕様になっていることがわかります。そこが魅力であり、また難しいところでもありますね。こういった部分をどんどん詰めていくのも楽しさの1つなのかもしれません。また、こういった知識を入れておくことで、こいつ出来る!人っぽくなれますねw(・ω・)
頭に'!'をつけることで一歩進んだコーディングも可能なわけです。カッコつけずにカッコつけられるって素敵じゃないですか。