概括
你能解释一下 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);
}();
你能给我解释一下吗?
它不起作用,因为它被解析为 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- 使用分配给变量 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;
};
尽管这是一个古老的问题和答案,但它讨论了一个至今仍让许多开发人员陷入困境的话题。我无法计算我采访过的 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 } Return NormalCompletion(empty)。
这个事实不容易观察,因为大多数尝试获取返回值的方法都会将函数声明转换为函数表达式。但是,eval
显示它:
var r = eval("函数 f(){}");控制台.log(r); // 不明确的
调用一个空的完成记录是没有意义的。这就是 function f(){}()
无法工作的原因。实际上 JS 引擎甚至不会尝试调用它,括号被认为是另一个语句的一部分。
但是如果你把函数用括号括起来,它就变成了一个函数表达式:
var r = eval("(函数 f(){})");控制台.log(r); // 函数 f(){}
函数表达式返回一个函数对象。因此您可以将其命名为:(function f(){})()
。
在 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);
以上所有将立即调用函数表达式。
我还有一点要说的。您的代码只需稍作改动即可工作:
var x = function(){
alert(2 + 2);
}();
我使用上面的语法而不是更广泛传播的版本:
var module = (function(){
alert(2 + 2);
})();
因为我没有设法让缩进在 vim 中的 javascript 文件中正常工作。似乎 vim 不喜欢左括号内的花括号。
function
关键字开头的任何有效 JavaScript 语句解释为 函数声明,在这种情况下,尾随 ()
被解释为 分组运算符 b> 根据 JavaScript 语法规则,它只能且 必须 包含 JavaScript 表达式。
()
包含在分组运算符中的变体(Douglas Crockford 强烈推荐的那个)一致性:使用 IIFE 而不将它们分配给变量是很常见的,如果您不始终如一地使用它们,很容易忘记包含那些包装括号。
(function(){
alert(2 + 2);
})();
以上是有效的语法,因为括号内传递的任何内容都被视为函数表达式。
function(){
alert(2 + 2);
}();
以上不是有效的语法。因为 java 脚本语法解析器在 function 关键字之后查找函数名称,因为它没有找到任何它抛出错误的东西。
也许更简短的答案是
function() { alert( 2 + 2 ); }
是定义(匿名)函数的函数字面量。在顶层不需要额外的 () 对,它被解释为表达式,只有文字。
(function() { alert( 2 + 2 ); })();
在调用匿名函数的表达式语句中。
它们可以与参数参数一起使用,例如
var x = 3;
var y = 4;
(function(a,b){alert(a + b)})(x,y)
结果为 7
这些额外的括号在全局命名空间和包含代码的匿名函数之间创建了额外的匿名函数。而在其他函数内部声明的 Javascript 函数只能访问包含它们的父函数的命名空间。由于全局范围和实际代码范围之间存在额外的对象(匿名函数),因此不会保留。
你也可以像这样使用它:
! function() { console.log('yeah') }()
或者
!! function() { console.log('yeah') }()
!
- 否定 op 将 fn 定义转换为 fn 表达式,因此,您可以使用 ()
立即调用它。与使用 0,fn def
或 void fn def
相同