web-dev-qa-db-ja.com

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

概要

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);
}();

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

358
Premasagar

FunctionDeclarationとして解析され、関数宣言の名前識別子がmandatoryであるため、機能しません。

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

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

function Identifier ( FormalParameterListopt ) { FunctionBody }

そしてFunctionExpressions:

function Identifieropt ( FormalParameterListopt ) { FunctionBody }

ご覧のとおり、IdentifierFunctionExpression(識別子opt)トークンはオプションであるため、名前が定義されていない関数式を使用できます。

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

またはnamed関数式:

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

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

2つのグラマープロダクションはあいまいな場合があり、次のようにまったく同じに見える場合があります。

function foo () {} // FunctionDeclaration

0,function foo () {} // FunctionExpression

パーサーは、それが現れる場所contextに応じて、FunctionDeclarationFunctionExpressionかを認識します。

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

一方、FunctionDeclarationsは、実際には「Program」コードと呼ばれるものにのみ表示されます。これは、グローバルスコープ外のコード、および他の関数のFunctionBody内のコードを意味します。

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

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

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

SyntaxError にはステートメントのみを含めることができるため(およびECMAScript仕様では関数ステートメントを定義していないため)、上記のコードは実際にBlockを生成する必要がありますが、ほとんどの実装は許容され、単純に2番目の関数を使用します、'false!'に警告するもの。

Mozillaの実装(Rhino、SpiderMonkey)の動作は異なります。それらの文法にはnon-standardFunction Statementが含まれています。つまり、関数は解析時ではなくrun-timeで評価されます。 、FunctionDeclarationsで起こるように。これらの実装では、定義された最初の関数を取得します。


関数はさまざまな方法で宣言できます 以下を比較

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

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-変数multiplyに割り当てられた名前付き関数式func_name

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

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

ただし、Premasagarのコードスニペットが名前識別子を与えたとしても機能しないということは、非常に重要なことです。

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

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

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

();

私が指摘したい別のことは、関数式に提供する名前識別子は、関数定義自体の中を除いてコードのコンテキストではほとんど役に立たないということです。

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

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

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

48
natlee75

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

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

FunctionDeclarationfunctionBindingIdentifier(FormalParameters){FunctionBody}

  1. Return NormalCompletion (empty)。

戻り値を取得しようとするほとんどの方法は、関数宣言を関数式に変換するため、この事実を観察するのは簡単ではありません。ただし、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(){})()

14
Oriol

JavaScriptでは、これは Immediately-Invoked Function Expression(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);

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

8
asmmahmud

ちょっとした発言があります。あなたのコードは小さな変更で動作します:

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

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

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

私はvimのjavascriptファイルでインデントを正しく動作させることができなかったからです。 vimは、開き括弧の中の波括弧を好まないようです。

3
Andrei Bozantan

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

var x = 3; 
var y = 4;

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

7になります

0
Jude

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

0
Jarkko Hietala

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

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

関数リテラル thatdefines(匿名)関数です。式として解釈される追加の()ペアは、トップレベルでは期待されず、リテラルのみです。

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

expression statement thatinvokes無名関数です。

0
theking2

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

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

または

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

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

0
Csaba K.
(function(){
     alert(2 + 2);
 })();

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

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

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

0
Vithy