解释封装的匿名函数语法

时间:2009-10-27 23:23:11

标签: javascript syntax anonymous-function

摘要

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

你能解释一下吗?

10 个答案:

答案 0 :(得分:389)

它不起作用,因为它被解析为 FunctionDeclaration ,函数声明的名称标识符是必需

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

FunctionDeclaration的语法如下:

function Identifier ( FormalParameterListopt ) { FunctionBody }

FunctionExpression s:

function Identifieropt ( FormalParameterListopt ) { FunctionBody }

正如您所看到Identifier中的FunctionExpression(标识符 opt )标记是可选的,因此我们可以使用没有定义名称的函数表达式:

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

命名函数表达式:

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

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

两种语法产生可能不明确,它们看起来完全相同,例如:

function foo () {} // FunctionDeclaration

0,function foo () {} // FunctionExpression

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

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

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

应该避免块内部的功能,因为它们可能导致不可预测的行为,例如:

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

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

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

Mozilla实现-Rhino,SpiderMonkey, - 有不同的行为。它们的语法包含一个非标准函数语句,这意味着该函数将在运行时进行评估,而不是在分析时进行评估,因为它发生在FunctionDeclaration秒。在这些实现中,我们将定义第一个函数。


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

1-使用分配给变量的<{3}}构造函数定义的函数乘以

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

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

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

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

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

4-命名函数表达式 func_name ,分配给变量乘法

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

答案 1 :(得分:48)

尽管这是一个古老的问题和答案,但它讨论了一个主题,直到今天仍然引发了许多开发人员的循环。我无法计算我采访过的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
};

当然,在调试代码时使用带有函数定义的名称标识符总是有用的,但这完全是另一回事......: - )

答案 2 :(得分:14)

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

  

14.1.20 - Runtime Semantics: Evaluation

     

FunctionDeclaration function BindingIdentifier ( FormalParameters ) { FunctionBody }

     
      
  1. 返回NormalCompletion(空)。
  2.   

这个事实并不容易观察,因为尝试获取返回值的大多数方法都会将函数声明转换为函数表达式。但是,eval显示了它:

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

调用空完成记录毫无意义。这就是为什么function f(){}()无法正常工作的原因。事实上,JS引擎甚至没有尝试调用它,括号被认为是另一个语句的一部分。

但是如果你将函数包装在括号中,它就变成了一个函数表达式:

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

函数表达式返回一个函数对象。因此,您可以调用它:(function f(){})()

答案 3 :(得分:8)

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

为了使它成为函数表达式,你需要:

  1. 使用()

  2. 将其括起来
  3. 在其前面放置一个void运算符

  4. 将其分配给变量。

  5. 否则它将被视为函数定义,然后您将无法通过以下方式同时调用/调用它:

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

    以上所有内容都会立即调用函数表达式。

答案 4 :(得分:3)

我还有一个小小的评论。您的代码只需稍加改动即可使用:

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

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

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

因为我没有设法让缩进在vim中正确处理javascript文件。似乎vim不喜欢开括号内的花括号。

答案 5 :(得分:0)

也许较短的答案是

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

函数文字定义(匿名)函数。另一个() - pair,被解释为一个表达式,不会出现在顶层,只有文字。

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

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

答案 6 :(得分:0)

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

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

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

上面的语法无效。因为java脚本语法分析器在函数关键字之后查找函数名称,因为它没有找到任何内容它会引发错误。

答案 7 :(得分:0)

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

var x = 3; 
var y = 4;

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

将导致7

答案 8 :(得分:0)

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

答案 9 :(得分:0)

您也可以像这样使用它:

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

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

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

相同