カプセル化された無名関数の構文を説明する


372

概要

JavaScriptでカプセル化された匿名関数の構文の背後にある理由を説明できますか?なぜこれは機能します(function(){})();が、機能しませんfunction(){}();か?


私が知っていること

JavaScriptでは、次のような名前付き関数を作成します。

function twoPlusTwo(){
    alert(2 + 2);
}
twoPlusTwo();

無名関数を作成して変数に割り当てることもできます。

var twoPlusTwo = function(){
    alert(2 + 2);
};
twoPlusTwo();

匿名関数を作成し、それを角かっこで囲んですぐに実行することで、コードブロックをカプセル化できます。

(function(){
    alert(2 + 2);
})();

これは、GreasemonkeyスクリプトやjQueryプラグインなどのように、変数が競合する可能性がある現在のスコープやグローバルスコープが乱雑になるのを避けるために、モジュール化されたスクリプトを作成するときに役立ちます。

今、私はこれがなぜ機能するのか理解しています。角かっこはコンテンツを囲み、結果のみを公開します(それを説明するためのより良い方法があると確信しています)(2 + 2) === 4


わからないこと

しかし、なぜこれも同様に機能しないのか理解できません。

function(){
    alert(2 + 2);
}();

それを説明してもらえますか?


39
これらのさまざまな表記法と、関数の定義/設定/呼び出しの方法は、最初にJavaScriptを操作するときに最も混乱する部分だと思います。人々も彼らについて話さない傾向があります。ガイドやブログで強調されているポイントではありません。それはほとんどの人にとって混乱するものであり、jsに堪能な人もそれを経験したに違いないので、私の心を吹き飛ばします。それは決して語られないこの空のタブーの現実のようなものです。
ahnbizcad 2014年

1
また、この構造目的について読むか、(技術的な説明を確認してください(こちらも)括弧の配置については、位置に関するこの質問を参照してください。
Bergi 14

OT:これらの匿名関数が
頻繁に

これは、即時に呼び出される関数式(IIFE)の典型的なケースです。
加納

回答:


410

として解析されているため機能しません。またFunctionDeclaration、関数宣言の名前識別子は必須です。

括弧で囲むと、として評価され、FunctionExpression関数式に名前を付けるかどうかを指定できます。

aの文法はFunctionDeclaration次のようになります。

function Identifier ( FormalParameterListopt ) { FunctionBody }

そしてFunctionExpressions:

function Identifieropt ( FormalParameterListopt ) { FunctionBody }

あなたが見ることができるようにIdentifier(識別子OPTを)トークンでは、FunctionExpressionしたがって、我々は定義された名前のない関数式を持つことができ、オプションであります:

(function () {
    alert(2 + 2);
}());

または名前付き関数式:

(function foo() {
    alert(2 + 2);
}());

括弧(以前はグループ化演算子と呼ばれていまし)は式のみを囲むことができ、関数式が評価されます。

2つの文法プロダクションはあいまいになる可能性があり、たとえば次のようにまったく同じに見える場合があります。

function foo () {} // FunctionDeclaration

0,function foo () {} // FunctionExpression

パーサーFunctionDeclarationFunctionExpression、それが現れるコンテキストに応じて、それがaかかを知っています。

上記の例では、カンマ演算子は式のみを処理することもできるため、2番目の式は式です。

一方、FunctionDeclarationsは実際には、「Program」コードと呼ばれるものだけに現れる可能性があります。これは、グローバルスコープの外側のコードと、FunctionBody他の関数の内側のコードを意味します。

ブロック内の関数は、予期しない動作を引き起こす可能性があるため、避ける必要があります。例:

if (true) {
  function foo() {
    alert('true');
  }
} else {
  function foo() {
    alert('false!');
  }
}

foo(); // true? false? why?

上記のコードは実際にはを生成するはずですSyntaxError。なぜなら、a Blockはステートメントのみを含むことができる(そしてECMAScript仕様は関数ステートメントを定義しない)ためですが、ほとんどの実装は許容され、2番目の関数(アラートを出す関数)を取るだけ'false!'です。

Mozillaの実装-Rhino、SpiderMonkey-は異なる動作をします。それらの文法には非標準の関数ステートメントが含まれています。つまり、関数はs で発生するように、解析時ではなく実行時に評価されますFunctionDeclaration。これらの実装では、最初の関数を定義します。


関数はさまざまな方法で宣言できます。以下を比較してください

1- 変数multiplyに割り当てられたFunctionコンストラクターで定義された関数

var multiply = new Function("x", "y", "return x * y;");

2- multiplyという名前の関数の関数宣言:

function multiply(x, y) {
    return x * y;
}

3-変数multiplyに割り当てられた関数式:

var multiply = function (x, y) {
    return x * y;
};

4-名前付き関数式func_nameは、変数multiplyに割り当てられています。

var multiply = function func_name(x, y) {
    return x * y;
};

2
CMSの答えは正しいです。関数の宣言と式の優れた詳細な説明については、kangaxによるこの記事を参照してください
ティムダウン、

これは素晴らしい答えです。これは、ソーステキストの解析方法やBNFの構造と密接に関連しているようです。あなたの例3では、等号の後に続くので、それが関数式であると言えるでしょうか?それは、それが単独で行に現れるとき、その形式が関数宣言/ステートメントであるのに対して?名前のない関数宣言として解釈されますが、名前がないのでしょうか?変数に割り当てたり、名前を付けたり、呼び出したりしない場合、それはどのような目的に役立ちますか?
ブルトン語

1
あは。非常に便利。ありがとう、CMS。あなたがにリンクされていることをMozillaのドキュメントのこの部分は特に啓発さ:developer.mozilla.org/En/Core_JavaScript_1.5_Reference/...
Premasagar

1
+1。ただし、関数式の間違った位置に閉じかっこがありました:-)
NickFitz

1
@GovindRai、いいえ。関数宣言はコンパイル時に処理され、重複する関数宣言は以前の宣言をオーバーライドします。実行時、関数宣言はすでに使用可能であり、この場合、使用可能なのは「false」を警告するものです。詳細については、JSを知らないことを
Saurabh Misra 2018年

50

これは古い質問と回答ですが、今日に至るまで多くの開発者にループを投げかけるトピックについて説明しています。私は私の関数宣言と関数式の違い伝えることができなかった私がインタビューしてきたJavaScriptの開発者候補の数数えることができないすぐに呼び出された関数式が何であるか全く分からなかったし。

ただし、非常に重要なことは、プレマサガーのコードスニペットに名前識別子を付けても機能しないことです。

function someName() {
    alert(2 + 2);
}();

これが機能しない理由は、JavaScriptエンジンがこれを関数宣言として解釈し、その後に式を含まない完全に関連のないグループ化演算子が続くためであり、グループ化演算子式を含む必要があるためです。JavaScriptによると、上記のコードスニペットは次のものと同等です。

function someName() {
    alert(2 + 2);
}

();

一部の人にとって役立つかもしれないことをもう1つ指摘しておきますが、関数式に指定する名前識別子は、関数の定義自体の中を除いて、コードのコンテキストではほとんど役に立ちません。

var a = function b() {
    // do something
};
a(); // works
b(); // doesn't work

var c = function d() {
    window.setTimeout(d, 1000); // works
};

もちろん、コードのデバッグに関しては、関数の定義で名前識別子を使用すると常に役立ちますが、それはまったく別のことです... :-)


17

すばらしい回答がすでに投稿されています。ただし、関数宣言が空の完了レコードを返すことに注意してください。

14.1.20-ランタイムセマンティクス:評価

FunctionDeclarationfunction BindingIdentifier ( FormalParameters ) { FunctionBody }

  1. NormalCompletion(空)を返します。

戻り値を取得しようとするほとんどの方法は関数宣言を関数式に変換するため、この事実を観察するのは簡単ではありません。しかし、evalそれを示しています:

var r = eval("function f(){}");
console.log(r); // undefined

空の完了レコードを呼び出しても意味がありません。それfunction f(){}()が機能しない理由です。実際、JSエンジンはそれを呼び出そうともせず、括弧は別のステートメントの一部と見なされます。

ただし、関数を括弧で囲むと、関数式になります。

var r = eval("(function f(){})");
console.log(r); // function f(){}

関数式は関数オブジェクトを返します。したがって、次のように呼び出すことができます(function f(){})()


残念なことに、この答えは見過ごされています。受け入れられた回答ほど包括的ではありませんが、いくつかの非常に有用な追加情報を提供し、より多くの投票に値します
Avrohom Yisroel 2017

10

JavaScriptでは、これは即時起動関数式(IIFE)と呼ばれます。

これを関数式にするには、次のことを行う必要があります。

  1. ()を使用して囲みます

  2. その前にvoid演算子を配置する

  3. それを変数に割り当てます。

それ以外の場合は、関数定義として扱われ、次の方法で同時に呼び出すことも呼び出すこともできません。

 function (arg1) { console.log(arg1) }(); 

上記はエラーになります。関数式をすぐに呼び出すことしかできないからです。

これはいくつかの方法で実現できます。方法1:

(function(arg1, arg2){
//some code
})(var1, var2);

方法2:

(function(arg1, arg2){
//some code
}(var1, var2));

方法3:

void function(arg1, arg2){
//some code
}(var1, var2);

方法4:

  var ll = function (arg1, arg2) {
      console.log(arg1, arg2);
  }(var1, var2);

上記のすべてはすぐに関数式を呼び出します。


3

もう1つだけ小さな発言があります。あなたのコードは小さな変更で動作します:

var x = function(){
    alert(2 + 2);
}();

より広く普及しているバージョンではなく、上記の構文を使用します。

var module = (function(){
    alert(2 + 2);
})();

vimでjavascriptファイルに対してインデントを正しく機能させることができなかったためです。vimは開き括弧内の中括弧が好きではないようです。


では、実行された結果を変数に代入するときにスタンドアロンではなく、この構文が機能するのはなぜですか?
paislee 2012

1
@paislee-JavaScriptエンジンは、functionキーワードで始まる有効なJavaScriptステートメントを関数宣言()として解釈するため、末尾は、JavaScript構文ルールに従って、JavaScript式のみを含むことができ、かつ含む必要があるグループ化演算子として解釈されるため。
natlee75 2012

1
@bosonix-推奨される構文は適切に機能しますが、一貫性を保つために、参照した「より広く普及したバージョン」または()グループ化演算子(Douglas Crockfordが強く推奨するもの)で囲まれたバリアントを使用することをお勧めします。変数に割り当てずにIIFEを使用することは一般的であり、一貫して使用しないと、それらの折り返し括弧を含めることを忘れがちです。
natlee75 2012

0

おそらく、より短い答えは

function() { alert( 2 + 2 ); }

ある関数リテラル定義(匿名)関数。式として解釈される追加の()ペアは、トップレベルでは想定されておらず、リテラルのみが想定されています。

(function() { alert( 2 + 2 ); })();

無名関数を呼び出す式ステートメント内にあります。


0
(function(){
     alert(2 + 2);
 })();

括弧内に渡されたものはすべて関数式と見なされるため、上記は有効な構文です。

function(){
    alert(2 + 2);
}();

上記は有効な構文ではありません。Javaスクリプト構文パーサーは何も見つからないのでエラーをスローするため、関数キーワードの後に​​関数名を探します。


2
あなたの答えは不正確ではありませんが、受け入れられた答えはすでにこのすべてを詳細にカバーしています。
アーノルドまで

0

次のように使用することもできます。

! function() { console.log('yeah') }()

または

!! function() { console.log('yeah') }()

!-否定opはfn定義をfn式に変換するため、ですぐに呼び出すことができます()0,fn defまたはを使用するのと同じvoid fn def


-1

これらは、次のようなパラメータ引数とともに使用できます。

var x = 3; 
var y = 4;

(function(a,b){alert(a + b)})(x,y)

7になります


-1

これらの追加の括弧は、グローバル名前空間とコードを含む匿名関数の間に追加の匿名関数を作成します。また、他の関数内で宣言されたJavaScript関数では、それらを含む親関数の名前空間にのみアクセスできます。グローバルスコープと実際のコードスコープの間に余分なオブジェクト(匿名関数)があるため、保持されません。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.