JavaScript函数前置感叹号!语法

213

我在几个库中看到了这种语法,我想知道它的好处是什么。(请注意,我非常清楚闭包和代码的作用,我只关心语法上的区别)

!function(){
  // do stuff
}();

作为更常见的替代方案之一

(function(){
  // do stuff
})();

关于自调用匿名函数,我有一些疑问。首先,是什么让顶部的例子实际上能够工作?为什么感叹号是必要的,才能使这个语句在语法上正确?我被告知+也可以在!的位置使用,还有其他什么东西吗?

其次,有什么好处?我唯一能看出来的就是它节省了一个字符,但我无法想象这样的好处足以吸引众多用户。是否还有其他好处我没有注意到?

我只能发现另一个区别是自调用函数的返回值,但在这两个示例中,我们并不真正关心函数的返回值,因为它仅用于创建闭包。所以,请问有人能告诉我为什么会使用第一种语法吗?


框架确实喜欢尽可能地节省字符,这是缩小代码无法优化的部分。 - alex
2
这两个例子都实现了相同的功能。正如所提到的,我理解这些函数在做什么,更感兴趣的是它们之间的语法差异。 - brad
@J.S. Taylor,我认为您误读了问题,但也许我对此有所错误... - JAAulde
8
我认为这是出于需要、必须更加高级地可爱,而非之前那一组人。"哦,那些用括号说话的异教徒,我们可以做得更好!" - Jared Farrish
2
我以前不知道这个,太棒了。我喜欢!,因为它强调了它正在被执行。 - cdmckay
显示剩余4条评论
7个回答

104

理想情况下,您应该能够仅使用以下方式完成所有操作:

function(){
  // do stuff
}(); 

这意味着声明匿名函数并立即执行它。但由于JavaScript语法的特殊性质,这种方式将无法工作。

因此,实现这个最短的形式是使用某些表达式,例如一元表达式(以及调用表达式):

!function(){
  // do stuff
}(); 

或者只是为了好玩:

-function(){
  // do stuff
}(); 

或者:

+function(){
  // do stuff
}(); 

甚至可以这样:

~function(){
  // do stuff
  return 0;
}( );

3
那么,简洁的唯一好处就是什么? - brad
12
我已将上述所有选项添加到性能测试中:http://jsperf.com/bang-function - Shaz
5
我会尽力进行表达的优化。在这种情况下,速度并不重要,因为它只运行一次。 - c-smile
1
出于好奇,我注意到感叹号和双感叹号执行最快,但在Chrome的演进过程中,感叹号至少比双感叹号更快了。 (可能统计上不显著,但......)这是为什么呢?我对引擎下面的代码了解不多,但觉得相当迷人。 - jmk2142
您IP地址为143.198.54.68,由于运营成本限制,当前对于免费用户的使用频率限制为每个IP每72小时10次对话,如需解除限制,请点击左下角设置图标按钮(手机用户先点击左上角菜单按钮)。 - user1106925

79

在Javascript中,以function开头的语句被期望为函数语句,并且应该长成这样:

function doSomething() {
}

类似于以下自执行函数:

function(){
  // do stuff
}();

不符合那种形式(并且将导致在第一个开括号处出现语法错误,因为没有函数名),所以方括号用于划分匿名函数表达式。

(function(){
  // do stuff
})();
但任何创建表达式的东西(与函数语句相反)都可以,因此有了“!”。它告诉解释器这不是一个函数语句。除此之外,运算符优先级规定在否定之前调用函数。
我不知道这个惯例,但如果它变得普遍起来,可能会提高可读性。我的意思是,任何阅读大块代码顶部的!function的人都会期望自我调用,就像我们已经习惯了看到(function时期望一样。只是我们会失去那些烦人的括号。我预计这就是原因,而不是速度或字符计数上的节省。

这个“以 function 开头的行应该是…” 看起来有点模糊。 那么这个怎么样: var foo = {CR/LF here} function bar() {} - c-smile

47

除了已经提到的内容,使用!语法在不使用分号的JavaScript中非常有用:

var i = 1
!function(){
  console.log('ham')
}()

i = 2
(function(){
  console.log('cheese')
})()

第一个示例按预期输出'ham',但第二个示例会出现错误,因为i = 2语句由于后面紧跟的括号而未终止。

此外,在连接的JavaScript文件中,如果前面的代码缺少分号,也不必担心。因此,没有必要使用常见的;(function(){})();来确保您自己的代码不会出错。

我知道我的回答有点晚,但我认为它还没有被提到过 :)


6
谢谢你的小费和美好回忆……如今没有人愿意写没有分号的JavaScript代码。 - Pablo Grisafi
4
Twitter Bootstrap是完全没有分号的JS,而且它还相当新颖……(如果上面的评论带有讽刺意味,我很抱歉我没有理解)。 - brandwaffle
2
这并不是真的。考虑以下示例:!function(){console.log("ham");}()!function(){console.log("cheese")}(); 你会得到一个错误:"SyntaxError: Unexpected token !"。 - heavysixer
是的,你说得对。如果把它们放在同一行上会出错。我没有考虑到那一点。但是,到目前为止我使用过的任何连接脚本/库都没有这样做。而且当你压缩和连接文件时会添加分号。所以,老实说,我想不到你会遇到这个问题的情况。另一方面,现在是凌晨2点,我可能很无知 :) - Smoe

6
一方面,jsPerf 表明使用 !(一元表达式)通常更快。有时它们会相等,但是当它们不相等时,我还没有看到非感叹号的版本在效率上太占优势: http://jsperf.com/bang-function 这是在最新的 Ubuntu 上测试的,使用了最旧的 Chrome 版本 8。所以结果可能会有所差异。
编辑:像 delete 这样的东西怎么样?
delete function() {
   alert("Hi!"); 
}();

或者 void
void function() {
   alert("Hi!"); 
}();

有趣!在Safari中,我得到了大约50/50的速度,非bang版本表现得很好。我想知道这种潜在速度差异是否有一些理论解释? - brad
@brad 这一定与Safari有关,如果你看一下测试,大多数其他浏览器似乎更喜欢使用!。但我也想找到一个理论来解释这个现象 :) - Shaz
3
我喜欢void,因为它明确表示“我不关心返回值”,同时也让我想起其他编程语言中的规范。 - code_monk

5

如您所见这里,在JavaScript中执行自调用方法的最佳方式是使用:

(function(){}()); -> 76,827,475 ops/sec

!function(){}();  -> 69,699,155 ops/sec

(function(){})(); -> 69,564,370 ops/sec

2
我怀疑性能是使用一种语法而不是另一种语法的原因。在每秒70M次操作下,声明闭包将比内部代码快数千倍。 - Eloims

5
因此,关于否定符号"!"和其他一元运算符(如+、-、~、delete和void),已经介绍了很多内容,总结起来就是:
!function(){
  alert("Hi!");
}(); 

或者

void function(){
  alert("Hi!");
}();

或者

delete function(){
  alert("Hi!");
}();

以下是一些有趣的使用二进制运算符的例子:

1 > function() {
   alert("Hi!"); 
}();

或者

1 * function() {
   alert("Hi!"); 
}();

或者
1 >>> function() {
   alert("Hi!"); 
}();

甚至可以
1 == function() {
   alert("Hi!"); 
}();

把三元运算留给其他人吧,伙计们 :)

离开这个三元运算符。


14
0?0:function() { alert("Hi!"); }();这段代码的意思是:如果0为真,则返回0,否则执行一个匿名函数并立即调用它,在弹出一个包含"Hi!"的警告框。 - daniel1426
太好了,丹!我们有三元的那个 ;) - Arman

0

这是一个很好的任务,可以使用void运算符,因为它可以强制表达式进行评估。

void function () { console.log('IIFE'); }();


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