ChatGPT解决这个技术问题 Extra ChatGPT

解释封装的匿名函数语法

概括

你能解释一下 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,或检查 (technical) explanation(也称为 here)。关于括号的位置,参见this question about their location
OT:想知道这些匿名函数在哪里被大量使用的朋友,请阅读adequatelygood.com/JavaScript-Module-Pattern-In-Depth.html
这是立即调用函数表达式 (IIFE) 的典型案例。

c
coolsaint

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

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

FunctionDeclaration 的语法如下所示:

function Identifier ( FormalParameterListopt ) { FunctionBody }

还有 FunctionExpression

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,具体取决于它出现的 context

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

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

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

if (true) { function foo() { alert('true'); } } else { function foo() { alert('false!'); } } foo(); // 真的?错误的?为什么?

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

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

函数可以用不同的方式声明,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
n
natlee75

尽管这是一个古老的问题和答案,但它讨论了一个至今仍让许多开发人员陷入困境的话题。我无法计算我采访过的 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
};

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


O
Oriol

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

14.1.20 - 运行时语义:评估 FunctionDeclaration : function BindingIdentifier (FormalParameters) { FunctionBody } Return NormalCompletion(empty)。

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

var r = eval("函数 f(){}");控制台.log(r); // 不明确的

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

但是如果你把函数用括号括起来,它就变成了一个函数表达式:

var r = eval("(函数 f(){})");控制台.log(r); // 函数 f(){}

函数表达式返回一个函数对象。因此您可以将其命名为:(function f(){})()


可惜这个答案被忽略了。虽然没有公认的答案那么全面,但它提供了一些非常有用的额外信息,值得更多投票
a
asmmahmud

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

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

使用 () 将其括起来,在将其分配给变量之前放置一个 void 运算符。

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

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

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

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

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

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


A
Andrei Bozantan

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

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 而不将它们分配给变量是很常见的,如果您不始终如一地使用它们,很容易忘记包含那些包装括号。
B
Barmar
(function(){
     alert(2 + 2);
 })();

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

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

以上不是有效的语法。因为 java 脚本语法解析器在 function 关键字之后查找函数名称,因为它没有找到任何它抛出错误的东西。


虽然您的答案不正确,但接受的答案已经涵盖了所有这些。
t
theking2

也许更简短的答案是

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

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

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

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


J
Jude

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

var x = 3; 
var y = 4;

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

结果为 7


J
Jarkko Hietala

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


S
Shakespear

你也可以像这样使用它:

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

或者

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

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