【问题标题】:Explain the encapsulated anonymous function syntax解释封装的匿名函数语法
【发布时间】:2010-12-10 16:39:35
【问题描述】:

总结

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

你能给我解释一下吗?

【问题讨论】:

  • 我认为所有这些不同的符号和定义/设置/调用函数的方式是最初使用 javascript 最令人困惑的部分。人们也倾向于不谈论它们。这不是指南或博客中的重点。这让我大吃一惊,因为这对大多数人来说都是令人困惑的事情,而精通 js 的人一定也经历过。这就像这个永远不会被谈论的空洞禁忌现实。
  • 另请阅读purpose of this construct,或查看(technicalexplanation(也可查看here)。有关括号的位置,请参阅this question about their location
  • OT:想知道这些匿名函数用在哪里的朋友,请阅读adequatelygood.com/JavaScript-Module-Pattern-In-Depth.html
  • 这是立即调用函数表达式 (IIFE) 的典型案例。

标签: javascript syntax anonymous-function


【解决方案1】:

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

当你用括号括起来时,它被评估为 FunctionExpression,并且函数表达式可以命名或不命名。

FunctionDeclaration 的语法如下所示:

function Identifier ( FormalParameterListopt ) { FunctionBody }

还有FunctionExpressions:

function Identifieropt ( FormalParameterListopt ) { FunctionBody }

如您所见,FunctionExpression 中的 Identifier (Identifieropt) 标记是可选的,因此我们可以有一个没有定义名称的函数表达式:

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

命名函数表达式:

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

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

这两个语法产生式可以是模棱两可的,它们可以看起来完全一样,例如:

function foo () {} // FunctionDeclaration

0,function foo () {} // FunctionExpression

解析器知道它是FunctionDeclaration 还是FunctionExpression,这取决于它出现的上下文

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

另一方面,FunctionDeclarations 实际上只能出现在所谓的“Program”代码中,这意味着代码在全局范围之外,在其他函数的FunctionBody 之内。

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

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

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

上面的代码实际上应该产生一个SyntaxError,因为Block只能包含语句(并且ECMAScript规范没有定义任何函数语句),但是大多数实现是宽容的,并且只会采用第二个函数,提醒'false!'

Mozilla 实现——Rhino、SpiderMonkey——有不同的行为。他们的语法包含一个非标准函数语句,这意味着函数将在运行时进行评估,而不是像FunctionDeclarations那样在解析时进行评估。在这些实现中,我们将定义第一个函数。


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

1- 使用分配给变量 multiplyFunction 构造函数定义的函数:

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

【讨论】:

  • CMS 的回答是正确的。有关函数声明和表达式的出色深入解释,请参阅this article by kangax
  • 这是一个很好的答案。它似乎与源文本的解析方式以及 BNF 的结构密切相关。在您的示例 3 中,我应该说它是一个函数表达式,因为它遵循一个等号,而当它单独出现在一行上时,它是一个函数声明/语句?我想知道这样做的目的是什么——它只是被解释为一个命名函数声明,但没有名字吗?如果您不将其分配给变量、命名或调用它,这有什么用?
  • 啊哈。很有用。谢谢,CMS。您链接到的这部分 Mozilla 文档特别有启发性:developer.mozilla.org/En/Core_JavaScript_1.5_Reference/…
  • +1,尽管您在函数表达式中的右括号位置错误:-)
  • @GovindRai,否。函数声明在编译时处理,重复的函数声明会覆盖先前的声明。在运行时,函数声明已经可用,在这种情况下,可用的就是警告“假”的那个。欲了解更多信息,请阅读you don't know JS
【解决方案2】:

尽管这是一个古老的问题和答案,但它讨论了一个至今仍让许多开发人员陷入困境的话题。我数不清有多少 JavaScript 开发人员候选人我采访过他们无法告诉我函数声明和函数表达式之间的区别谁不知道立即调用的函数表达式是什么.

不过,我想提一下一件非常重要的事情,那就是 Premasagar 的代码 sn-p 即使他给了它一个名称标识符,它也无法工作。

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

这不起作用的原因是 JavaScript 引擎将其解释为一个函数声明,后跟一个完全不相关的不包含表达式的分组运算符,并且分组运算符必须包含一个表达式。根据JavaScript,上面sn-p的代码相当于下面的代码。

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】:

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

    14.1.20 - Runtime Semantics: Evaluation

    FunctionDeclaration : function BindingIdentifier ( FormalParameters ) { FunctionBody @987654327 @

    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】:

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

    为了使它成为一个函数表达式,你必须:

    1. 用 () 括起来

    2. 在其前面放置一个 void 运算符

    3. 将其分配给变量。

    否则会被当作函数定义,然后你将无法通过以下方式同时调用/调用它:

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

    上面会给你错误。因为您只能立即调用函数表达式。

    这可以通过以下几种方式实现: 方式一:

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

    方式二:

    (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】:

      我还有一点要说的。您的代码只需稍作改动即可工作:

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

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

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

      因为我没有设法让缩进在 vim 中的 javascript 文件中正常工作。似乎 vim 不喜欢左括号内的花括号。

      【讨论】:

      • 那么为什么当你将执行结果分配给一个变量而不是独立的时候,这个语法会起作用呢?
      • @paislee -- 因为 JavaScript 引擎将任何以 function 关键字开头的有效 JavaScript 语句解释为 函数声明 在这种情况下,尾随 () 被解释为分组运算符,根据 JavaScript 语法规则,它只能且必须包含 JavaScript 表达式。
      • @bosonix -- 您的首选语法运行良好,但最好使用您引用的“更广泛传播的版本”或 () 包含在分组运算符中的变体( Douglas Crockford 强烈推荐的一种)以保持一致性:通常使用 IIFE 而不将它们分配给变量,如果您不始终如一地使用它们,很容易忘记包括那些环绕括号。
      【解决方案6】:
      (function(){
           alert(2 + 2);
       })();
      

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

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

      以上不是有效的语法。因为 java 脚本语法解析器在 function 关键字之后查找函数名,因为它没有找到任何内容,所以会引发错误。

      【讨论】:

      • 虽然您的答案不正确,但接受的答案已经涵盖了所有这些。
      【解决方案7】:

      也许更简短的答案是

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

      是一个函数字面量,它定义一个(匿名)函数。在顶层不需要额外的 () 对,它被解释为表达式,只有文字。

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

      调用匿名函数的表达式语句中。

      【讨论】:

        【解决方案8】:

        你也可以像这样使用它:

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

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

        ! - 否定运算将 fn 定义转换为 fn 表达式,因此,您可以立即使用 () 调用它。与使用0,fn defvoid fn def 相同

        【讨论】:

          【解决方案9】:

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

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

          结果为 7

          【讨论】:

            【解决方案10】:

            那些额外的括号在全局命名空间和包含代码的匿名函数之间创建了额外的匿名函数。并且在其他函数内部声明的 Javascript 函数只能访问包含它们的父函数的命名空间。由于全局范围和实际代码范围之间存在额外的对象(匿名函数)。

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 1970-01-01
              • 2011-05-09
              • 1970-01-01
              • 1970-01-01
              • 2017-11-20
              • 1970-01-01
              • 2014-01-24
              相关资源
              最近更新 更多