括号在对象/函数/类声明周围的含义是什么?

318

YUI库的示例中,您可以找到许多使用此构造的示例:

(function() {
    var Dom = YAHOO.util.Dom,
    Event = YAHOO.util.Event,
    layout = null,
        ...
})();

我认为最后一对括号是在声明函数后立即执行该函数。

... 但是围绕函数声明的前一组括号呢?

我认为这是范围的问题;它可以隐藏内部变量以外部函数和可能的全局对象。是吗?更普遍地说,这些括号的机制是什么?


3
这也在 Difference between (function(){})(); and function(){}(); 中进行了讨论。 - Walter Rumsby
13
我认为我们需要保留这两个重复的条目,因为它们的表述方式非常不同。 - Jeff Atwood
1
这是不删除的一个好理由,但我不明白为什么我们不能将其作为重复关闭。 - Roger Pate
3
这个问题在之前的链接问题中没有被问过。 - Nabil Kadimi
7个回答

239

45
在这种情况下,括号只是必需的,因为在JavaScript中,“function”可以是语句或表达式,这取决于上下文。括号强制将其视为表达式。即使没有周围的括号,以下代码也是有效的:“var x = function(){return 1}()”。只有一个函数表达式和该表达式的应用程序“(...)”。在这种情况下,建议在开头的括号之前放置“;”:我编写无分号的代码,并且以“(”开头的行可能被解析为从上一行继续的表达式。 - user166390
@user166390 => 为什么在“}”后面加上“()”? - Alexander
等一下...所以这个混乱的代码只是为了绕过 JS 使用的奇怪作用域而进行的一个 hack 吗? - Beska
1
@Beska:不完全是这样。JavaScript的作用域是函数作用域(广义上),这并不那么奇怪。而且函数可以在单个表达式中声明和执行。更公正的说法是,使用它来实现“私有”变量的“黑科技”更多地是由于JavaScript缺乏成熟的模块系统。 - Andy Hume

152

Andy Hume 已经 给出 了答案,我只想再添加一些细节。

使用这个结构,你正在创建一个具有自己的评估环境或闭包的匿名函数,然后立即对它进行评估。好处在于,你可以访问在匿名函数之前声明的变量,并且你可以在此函数中使用局部变量,而不会意外地覆盖现有变量。

使用var关键字非常重要,因为在JavaScript中,默认情况下每个变量都是全局的,但是使用关键字你可以创建一个新的、词法作用域变量,也就是说,它可以被代码在两个大括号之间看到。在你的例子中,你实际上是在为YUI库中的对象创建短别名,但它还有更强大的用途。

我不想让你没有代码示例,所以我将放置一个简单的示例来说明闭包:

var add_gen = function(n) {
  return function(x) {
    return n + x;
  };
};
var add2 = add_gen(2);
add2(3); // result is 5

在函数add_gen中,您正在创建另一个函数,该函数将简单地将数字n添加到其参数中。诀窍在于,在函数参数列表中定义的变量充当词法作用域变量,就像使用var定义的变量一样。
返回的函数在add_gen函数的大括号之间定义,因此即使add_gen函数执行完成后,它仍将访问n的值,这就是为什么在执行示例的最后一行时会得到5的原因。
借助函数参数具有词法作用域,您可以解决在匿名函数中使用循环变量引起的“问题”。以一个简单的例子为例:
for(var i=0; i<5; i++) {
  setTimeout(function(){alert(i)}, 10);
}

"expected"结果可能是从零到四的数字,但你却得到了四个5的实例。这是因为setTimeout中的匿名函数和for循环都使用了完全相同的i变量,所以当函数被评估时,i将会是5。
通过使用你问题中的技术和函数参数在词法上作用域的事实,你可以获得天真的期望结果。(我在other answer中使用了这种方法)
for(var i=0; i<5; i++) {
  setTimeout(
     (function(j) {
       return function(){alert(j)};
     })(i), 10);
}

在外部函数的立即评估中,每次迭代都会创建一个完全独立的变量j,并且i的当前值将被复制到该变量中,因此您将获得最初尝试时期望的结果。

我建议您尝试理解http://ejohn.org/apps/learn/上的优秀教程,以更好地理解闭包,那是我学到非常多的地方。


46

...但是之前所有函数声明周围的括号怎么办?

具体而言,它会使JavaScript将'function() {...}'结构解释为内联匿名函数表达式。如果您省略了括号:

function() {
    alert('hello');
}();

您会得到一个语法错误,因为JS解析器会看到'function'关键字并假定您正在开始一个函数语句,形式如下:

function doSomething() {
}

如果没有函数名,就不能有函数语句。

函数表达式和函数语句是两种不同的构造方式,它们的处理方式也非常不同。不幸的是,它们的语法几乎完全相同,所以不仅程序员很容易混淆,解析器也很难分辨你想用哪个!


14
只是跟进一下Andy Hume和其他人所说的:
匿名函数周围的'()'是ECMA规范第11.1.6节中定义的'分组运算符':http://www.ecma-international.org/publications/files/ECMA-ST/Ecma-262.pdf
直接从文档中摘录如下:
11.1.6 分组运算符
产生式PrimaryExpression : ( Expression ) 的评估如下:
1. 返回评估Expression的结果。这可能是引用类型。
在这个上下文中,函数被视为表达式。

7
在这个主题上需要考虑以下几点:

  • The parenthesis:

    The browser (engine/parser) associates the keyword function with

    [optional name]([optional parameters]){...code...}
    

    So in an expression like function(){}() the last parenthesis makes no sense.

    Now think at

    name=function(){} ; name() !?
    

是的,第一对括号将匿名函数转化为变量(存储表达式),第二个则启动了评估/执行,因此function(){})()是有意义的。

  • 实用性: ?

    1. 用于在加载时执行某些代码,并将使用的变量与页面的其余部分隔离开来,特别是当可能存在名称冲突时;

    2. 将 eval("string") 替换为

      (new Function("string"))()

    3. 用 " =?: " 运算符包装长代码,例如:

      result = exp_to_test ? (function(){... long_code ...})() : (function(){...})();


对于形如 function name(){}() 的函数,变量 name 会被隐式创建,因此在代码定义后放置大括号相当于调用了 name()。 - bortunac

4
第一个括号,可以理解为运算顺序。围绕函数定义的括号集合的“结果”是函数本身,而第二组括号确实执行该函数。
至于为什么这很有用,我不是一个足够出色的JavaScript专家,所以无法给出任何想法。 :P

1
请参见此问题。如果您使用函数名称,则不需要第一组括号,但是无名称的函数需要此结构,并且括号用于使编码人员在浏览代码时意识到他们正在查看自调用函数(请参见某位博主的最佳实践建议)。

1
如果您使用的是命名函数,那么需要括号,但如果您将函数的结果分配给变量,则可以省略括号。例如,function myResult() { ... }() 将失败,但 var myResult = function { ... }() 将起作用。 - wheresrhys
你的最佳实践链接中有很好的解释。 - David

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接