解释封装的匿名函数语法

总结

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

你能给我解释一下吗?

79640 次浏览

它不能工作,因为它被解析为FunctionDeclaration,而函数声明的名称标识符是强制性的

当你用圆括号包围它时,它被计算为FunctionExpression,函数表达式可以命名也可以不命名。

FunctionDeclaration的语法是这样的:

function Identifier ( FormalParameterListopt ) { FunctionBody }

FunctionExpressions:

function Identifieropt ( FormalParameterListopt ) { FunctionBody }

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

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

命名函数表达式:

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

括号(正式名称为分组操作符)只能包围表达式,函数表达式被求值。

这两个语法结果可以是模棱两可的,它们可以看起来完全相同,例如:

function foo () {} // FunctionDeclaration


0,function foo () {} // FunctionExpression

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

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

另一方面,__abc0实际上只能出现在所谓的“Program"code,意思是全局作用域之外的代码,以及其他函数的FunctionBody内部的代码。

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

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


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

上面的代码实际上应该生成SyntaxError,因为Block只能包含语句(ECMAScript规范没有定义任何函数语句),但大多数实现都是容忍的,并且只接受第二个函数,即警告'false!'的函数。

Mozilla的实现——rhino、SpiderMonkey——有不同的行为。它们的语法包含非标准的函数语句,这意味着函数将在运行时执行,而不是在解析时执行,因为它发生在FunctionDeclarations中。在这些实现中,我们将得到定义的第一个函数。


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

1-用函数构造函数定义的函数,赋值给变量:

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

2- 函数的函数声明:

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

赋值给变量的函数表达式:

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

命名函数表达式func_name,赋值给变量:

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

我还有一个小问题。您的代码将工作与一个小的变化:

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

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

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

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

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

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

也许简短的回答是这样的

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

是一个字面函数,该定义了是一个(匿名)函数。附加的()-对被解释为表达式,不需要在顶层,只需要字面量。

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

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

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

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

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

以上语法无效。因为java脚本语法分析器在函数关键字之后查找函数名,因为它没有找到任何东西,所以抛出一个错误。

很棒的答案已经被贴出来了。但我想指出,函数声明返回一个空的完成记录:

14.1.20 -运行时语义:评估

__abc5: __abc0 __abc6 __abc1 __abc7 __abc2 __abc3 __abc8 __abc4

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

它们可以与参数一起使用

var x = 3;
var y = 4;


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

结果是7

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

为了使它成为一个函数表达式,你必须:

  1. 使用()将其括起来

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

  3. 赋值给一个变量

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

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

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

这可以通过以下几种方式实现: 方法1:< / p >
(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);

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

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

你也可以这样使用它:

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

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

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