代码之家  ›  专栏  ›  技术社区  ›  Prem

解释封装的匿名函数语法

  •  349
  • Prem  · 技术社区  · 15 年前

    总结

    你能解释一下在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);
    }();
    

    你能给我解释一下吗?

    9 回复  |  直到 6 年前
        1
  •  381
  •   Jack Bashford    6 年前

    它不起作用,因为它被解析为 FunctionDeclaration ,函数声明的名称标识符为 强制性的 .

    当您用括号将其括起来时,它将被评估为 FunctionExpression 和函数表达式是否可以命名。

    A的语法 函数声明 如下所示:

    function Identifier ( FormalParameterListopt ) { FunctionBody }
    

    函数表达式 S:

    function Identifieropt ( FormalParameterListopt ) { FunctionBody }
    

    你可以看到 Identifier (标识符) 选择 令牌 函数表达式 是可选的,因此我们可以有一个没有定义名称的函数表达式:

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

    命名 函数表达式:

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

    括号(正式称为 the Grouping Operator )只能环绕表达式,并计算函数表达式。

    这两种语法产物可能不明确,并且看起来完全相同,例如:

    function foo () {} // FunctionDeclaration
    
    0,function foo () {} // FunctionExpression
    

    解析器知道它是否是 函数声明 或A 函数表达式 ,取决于 语境 它出现的地方。

    在上面的示例中,第二个是表达式,因为 Comma operator 也只能处理表达式。

    另一方面, 函数声明 S实际上只能出现在 Program “代码,表示代码在全局范围之外,在 FunctionBody 其他功能。

    应避免使用块内的函数,因为它们可能导致不可预测的行为,例如:

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

    上面的代码实际上应该产生一个 SyntaxError ,因为一个 Block 只能包含语句(并且ECMAScript规范没有定义任何函数语句),但大多数实现都是可容忍的,并且只接受第二个函数,即警报的函数 'false!' .

    Mozilla实现——Rhino、Spidermonkey——有不同的行为。他们的语法包含 非标准 函数语句,表示函数将在 运行时间 ,而不是分析时,就像它发生在 函数声明 在这些实现中,我们将得到定义的第一个函数。


    函数可以用不同的方式声明, compare the following :

    1-用 Function 分配给变量的构造函数 :

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

    2-名为的函数的函数声明 :

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

    3-分配给变量的函数表达式 :

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

    4-命名函数表达式 函数名 ,分配给变量 以下内容:

    var multiply = function func_name(x, y) {
        return x * y;
    };
    
        2
  •  45
  •   natlee75    12 年前

    尽管这是一个古老的问题和答案,但它讨论了一个至今仍让许多开发人员陷入循环的主题。我无法计算我采访过的那些不能告诉我函数声明和函数表达式之间区别的javascript开发人员候选人的数量。 他不知道什么是立即调用的函数表达式。

    不过,我想提一件非常重要的事情,那就是,即使premasagar给了它一个名称标识符,它的代码片段也不会工作。

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

    这不起作用的原因是JavaScript引擎将其解释为一个函数声明,后面是一个完全不相关的分组运算符(不包含表达式)和分组运算符 必须 包含表达式。根据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
    };
    

    当然,在调试代码时,在函数定义中使用名称标识符总是很有帮助的,但这完全是另外一回事…-)

        3
  •  13
  •   Oriol    8 年前

    伟大的答案已经发布。但我要注意的是,函数声明返回一个空的完成记录:

    14.1.20 - Runtime Semantics: Evaluation

    函数声明 : function 绑定标识符 ( 形式参数 ) { 功能体 }

    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(){})() .

        4
  •  7
  •   asmmahmud    7 年前

    在javascript中,这称为 Immediately-Invoked Function Expression (IIFE) .

    要使其成为函数表达式,必须:

    1. 使用()将其括起来

    2. 在它前面放置一个空操作符

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

    以上所有操作都将立即调用函数表达式。

        5
  •  3
  •   Andrei Bozantan    13 年前

    我还有一句话要说。您的代码只需稍作更改即可使用:

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

    我使用上述语法而不是更广泛的版本:

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

    因为我没能让缩进正确地用于VIM中的javascript文件。似乎Vim不喜欢左括号内的花括号。

        6
  •  0
  •   theking2    9 年前

    也许更短的答案是

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

    是一个 函数显式声明 那个 定义 (匿名)函数。附加的()-对(解释为表达式)不应出现在顶层,只应出现文本。

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

    是在 表达式语句 那个 调用 匿名函数。

        7
  •  0
  •   Barmar    8 年前
    (function(){
         alert(2 + 2);
     })();
    

    以上是有效的语法,因为括号内传递的任何内容都被视为函数表达式。

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

    上面的语法无效。因为Java脚本语法分析器在函数关键字后面查找函数名,因为它找不到任何东西,所以它抛出了一个错误。

        8
  •  0
  •   Jude    7 年前

    它们可以与参数参数一起使用,例如

    var x = 3; 
    var y = 4;
    
    (function(a,b){alert(a + b)})(x,y)
    

    结果是7

        9
  •  0
  •   Jarkko Hietala    6 年前

    这些额外的括号在全局命名空间和包含代码的匿名函数之间创建额外的匿名函数。在javascript中,在其他函数中声明的函数只能访问包含它们的父函数的命名空间。因为在全局范围和实际代码范围之间有额外的对象(匿名函数),所以不会保留范围。