解释封装的匿名函数语法


372

摘要

您能解释JavaScript中封装的匿名函数的语法背后的原因吗?为何起作用:(function(){})();但这不起作用function(){}();


我知道的

在JavaScript中,将创建一个命名函数,如下所示:

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

您还可以创建一个匿名函数并将其分配给变量:

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

您可以通过创建一个匿名函数来封装代码块,然后将其包装在方括号中并立即执行:

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

在创建模块化脚本时,这很有用,以避免因潜在冲突的变量而使当前范围或全局范围混乱(例如Greasemonkey脚本,jQuery插件等)。

现在,我明白了为什么这样做了。方括号将内容括起来,仅显示结果(我敢肯定有一种更好的描述方式),例如with (2 + 2) === 4


我不明白的

但是我不明白为什么这不能同样有效:

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

你能跟我解释一下吗?


39
我认为所有这些不同的符号以及定义/设置/调用函数的方式都是最初使用javascript时最容易混淆的部分。人们也往往不谈论他们。这不是指南或博客中的重点。这让我大吃一惊,因为这对于大多数人来说都是一件令人困惑的事情,精通js的人也一定会经历过。就像这个空洞的禁忌现实,从来没有被谈论过。
ahnbizcad 2014年

1
还请阅读有关此构造目的的信息,或查看(技术说明(也在此处)。有关括号的位置,请参阅有关其位置的问题
Bergi 2014年

OT:对于那些想知道在哪里使用这些匿名函数的人来说,请阅读充足的信息
。good/ com / JavaScript

这是立即调用函数表达式(IIFE)的典型情况。
阿米奴·卡诺

Answers:


410

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

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

a的语法FunctionDeclaration如下:

function Identifier ( FormalParameterListopt ) { FunctionBody }

FunctionExpressions:

function Identifieropt ( FormalParameterListopt ) { FunctionBody }

如您所见,Identifier(Identifier opt)令牌FunctionExpression是可选的,因此我们可以使用没有定义名称的函数表达式:

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

命名函数表达式:

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

括号(正式称为分组运算符)只能包围表达式,并且会评估函数表达式。

这两个语法产生可能会模棱两可,并且看起来可能完全相同,例如:

function foo () {} // FunctionDeclaration

0,function foo () {} // FunctionExpression

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

在上面的示例中,第二个是表达式,因为逗号运算符也只能处理表达式。

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

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

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

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

上面的代码实际上应该产生一个SyntaxError,因为a Block只能包含语句(并且ECMAScript规范未定义任何函数语句),但是大多数实现都是可以容忍的,并且将仅采用第二个函数,即alert的第二个函数'false!'

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


可以用不同的方式声明函数,请比较以下内容

1-一个函数,该函数使用分配给变量的Function构造函数乘法

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

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

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

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

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

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

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

2
CMS的答案是正确的。有关函数声明和表达式的出色深入解释,请参阅kangax的本文
Tim Down

这是一个很好的答案。它似乎确实与源文本的解析方式以及BNF的结构密切相关。在您的示例3中,我应该说它是一个函数表达式,因为它跟随等号,而当窗体本身出现在一行上时,窗体是函数声明/声明吗?我不知道这样做的目的是-它只是解释为命名函数声明,而没有名称吗?如果您不将其分配给变量,命名或调用它,这有什么目的?
布列塔尼

1
啊哈 很有用。谢谢,CMS。Mozilla的文档,你联系的这个部分是有启发:developer.mozilla.org/En/Core_JavaScript_1.5_Reference/...
Premasagar

1
+1,尽管函数表达式中的

1
@GovindRai,否。函数声明是在编译时处理的,并且重复的函数声明会覆盖以前的声明。在运行时,该函数声明已经可用,在这种情况下,可用的一个是警告“ false”的声明。有关更多信息,请阅读您不知道JS
Saurabh Misra,

50

即使这是一个古老的问答,它仍然讨论了一个主题,该主题至今仍使许多开发人员陷入困境。我无法计算我采访过的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
};

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


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

10

在javascript中,这称为立即调用函数表达式(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));

方式三:

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

方式4:

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

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


3

我还有一点话要说。您的代码将进行一些小的更改:

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

1
@bosonix-您首选的语法效果很好,但是最好使用您引用的“更广泛版本”或()分组运算符(Douglas Crockford强烈建议使用)中包含的变体来保持一致性:通常不使用IIFE而不将它们分配给变量,并且如果您使用不一致的话,很容易忘记包含那些包装括号。
natlee75

0

也许简短的答案是

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

是一个函数字面限定一个(匿名)函数。附加的()对,被解释为一个表达式,不希望在顶层使用,仅在文字上使用。

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

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


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

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

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

上面的语法无效。因为Java脚本语法分析器在function关键字之后查找功能名称,因为它找不到任何东西,因此会引发错误。


2
尽管您的答案是正确的,但已接受的答案已经涵盖了所有部门。
Till Arnold

0

您也可以像这样使用它:

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

要么

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

!-negation 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函数中,只能访问包含它们的父函数的名称空间。由于在全局范围和实际代码范围之间不存在额外的对象(匿名函数)。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.