解释封装的匿名函数语法

问题描述 投票:354回答:10

摘要

你能解释一下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 syntax anonymous-function
10个回答
396
投票

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

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

FunctionDeclaration的语法如下:

function Identifier ( FormalParameterListopt ) { FunctionBody }

FunctionExpressions:

function Identifieropt ( FormalParameterListopt ) { FunctionBody }

如您所见,Identifier中的FunctionExpression(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, - 具有不同的行为。它们的语法包含一个非标准的Function语句,这意味着该函数将在运行时进行评估,而不是在解析时进行评估,就像FunctionDeclarations一样。在这些实现中,我们将定义第一个函数。


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

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

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

0
投票

您也可以使用它:

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

要么

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

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


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

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


14
投票

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

14.1.20 - Runtime Semantics: Evaluation

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


8
投票

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

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

  1. 用()括起来
  2. 在它之前放置一个void运算符
  3. 将其分配给变量。

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

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

以上都会立即调用函数表达式。


3
投票

我只是另一个小话。您的代码只需稍加改动即可:

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

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

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

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


0
投票

也许更简短的答案就是那样

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

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

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

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


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

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

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

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


0
投票

它们可以与参数参数一起使用

var x = 3; 
var y = 4;

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

会导致7


0
投票

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

© www.soinside.com 2019 - 2024. All rights reserved.