解释封装的匿名函数语法

IT技术 javascript syntax anonymous-function
2021-01-23 01:54:10

概括

你能解释一下 JavaScript 中封装匿名函数的语法背后的原因吗?为什么这样做:(function(){})();但这不起作用:function(){}();


我知道的

在 JavaScript 中,可以创建一个像这样的命名函数:

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

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

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

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

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

这在创建module化脚本时很有用,以避免将当前范围或全局范围与潜在的冲突变量混淆 - 例如 Greasemonkey 脚本、jQuery 插件等。

现在,我明白为什么会这样了。括号括起内容并仅显示结果(我相信有更好的方法来描述),例如使用(2 + 2) === 4.


我不明白的

但我不明白为什么这也不能正常工作:

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

你能向我解释一下吗?

6个回答

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

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

a 的语法FunctionDeclaration如下所示:

function Identifier ( FormalParameterListopt ) { FunctionBody }

FunctionExpression

function Identifieropt ( FormalParameterListopt ) { FunctionBody }

正如你所看到的Identifier(Identifier opt ) 标记FunctionExpression是可选的,因此我们可以有一个没有定义名称的函数表达式:

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

命名函数表达式:

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

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

这两个语法产生式可能有歧义,它们看起来完全一样,例如:

function foo () {} // FunctionDeclaration

0,function foo () {} // FunctionExpression

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

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

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

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

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

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

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

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


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

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

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,分配给变量multiply

var multiply = function func_name(x, y) {
    return x * y;
};
+1,尽管您在函数表达式中的右括号位置错误:-)
2021-03-10 01:54:10
CMS 的回答是正确的。有关函数声明和表达式的深入解释,请参阅kangax 的这篇文章
2021-03-18 01:54:10
@GovindRai,否。函数声明在编译时处理,重复的函数声明会覆盖先前的声明。在运行时,函数声明已经可用,在这种情况下,可用的是警告“false”的那个。欲了解更多信息,请阅读你不懂 JS
2021-03-21 01:54:10
这是一个很好的答案。它似乎与源文本的解析方式和 BNF 的结构密切相关。在您的示例 3 中,我应该说它是一个函数表达式,因为它跟在等号之后,而该形式是一个函数声明/语句,当它单独出现在一行上时?我想知道这样做的目的是什么 - 它是否只是解释为命名函数声明,但没有名称?如果您不将它分配给一个变量、命名它或调用它,那这有什么用呢?
2021-03-24 01:54:10
啊哈。很有用。谢谢,内容管理系统。您链接到的 Mozilla 文档的这一部分特别具有启发性:developer.mozilla.org/En/Core_JavaScript_1.5_Reference/...
2021-03-31 01:54:10

尽管这是一个古老的问题和答案,但它讨论了一个直到今天让许多开发人员陷入困境的话题。我不能指望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
};

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

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

14.1.20 - 运行时语义:评估

FunctionDeclaration : function 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(){})()

可惜这个答案被忽视了。虽然不像公认的答案那么全面,但它提供了一些非常有用的额外信息,值得更多投票
2021-03-22 01:54:10

在 javascript 中,这称为立即调用函数表达式 (IIFE)

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

  1. 使用 () 将其括起来

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

  3. 将它分配给一个变量。

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

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

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

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

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

方式二:

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

方式三:

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

方式四:

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

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

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

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

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

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

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

@bosonix - 您的首选语法效果很好,但最好使用您引用的“更广泛传播的版本”或()包含在分组运算符(Douglas Crockford 强烈推荐的那个)中的变体以保持一致性:它是使用 IIFE 而不将它们分配给变量是很常见的,如果您不始终如一地使用它们,很容易忘记包含那些包装括号。
2021-03-12 01:54:10
那么为什么当您将执行的结果分配给变量时这种语法有效,而不是独立的?
2021-03-13 01:54:10
@paislee——因为 JavaScript 引擎会将任何以function关键字开头的有效 JavaScript 语句解释函数声明,在这种情况下,尾随()被解释为分组运算符,根据 JavaScript 语法规则,该运算符只能且必须包含 JavaScript 表达式。
2021-03-25 01:54:10