在JavaScript中,!function(){}()相对于(function () {})()的优点是什么?

87

可能重复:
在函数前面加上感叹号(!)是什么意思?

我长期以来都在JavaScript中使用以下代码来创建自执行的匿名函数:

(function () { /* magic happens */ })()

最近,我开始看到更多以下模式的实例(例如,在Bootstrap中):

!function () { /* presumably the same magic happens */ }()

有人知道第二种模式的优点是什么吗?或者,这只是一种风格偏好吗?


1
请看这篇文章,它解释了两者之间的区别:https://dev59.com/PG865IYBdhLWcg3wkPWD - Clive
2
这个答案的链接:https://dev59.com/PG865IYBdhLWcg3wkPWD#3755656 - Michael Jasper
2
我会说这个答案:https://dev59.com/PG865IYBdhLWcg3wkPWD#5654929 - Ivar Bonsaksen
6
为了强制在表达式上下文中评估一个函数并立即调用它而忽略其返回值,这两种方法(使用圆括号进行分组和使用逗号运算符)是等效的。但是我个人认为,使用分组运算符(即圆括号)更加“语义正确”(可能也更常见和易读),因为这就是该运算符的目的,评估表达式...只是我的个人看法...祝好! - Christian C. Salvadó
1
我认为这个问题被关闭了并列为重复问题很令人难过 - 这个问题及其所有回答都比重复中的任何内容都好。 - Skilldrick
6个回答

82

这两种不同的技术在功能上有所区别, 外观也不同。其中一种技术优于另一种的潜在优点将源于这些差异。

简洁性

Javascript是一种语言,简洁性非常重要,因为Javascript是在页面加载时下载的。这意味着Javascript越简洁,下载时间就越快。出于这个原因,有Javascript 压缩器混淆器 可以压缩Javascript文件以优化下载时间。例如,alert ( "Hi" ) ; 中的空格将被优化为 alert("Hi");

记住这一点,比较这两种模式:

  • 普通 闭包:   (function(){})() 16个字符
  • 否定闭包: !function(){}() 15个字符
这只是一个微小的优化,除非你在进行代码高尔夫比赛,否则我不认为这是一个特别有说服力的论点。

取反返回值

比较ab的结果值。

var a = (function(){})()
var b = !function(){}()

由于函数 a 没有返回任何值,因此 a 将会是 undefined。由于对 undefined 的否定是 trueb 将被评估为 true。这对那些想要否定函数返回值或者有一个“一切都必须返回非空或未定义值”的迷恋的人来说是一个优势。您可以在 其他Stack Overflow问题 上查看这种工作方式的解释。
我希望这能帮助您理解这个函数声明背后的原理,这通常被认为是一个反模式

5
如果意图只是执行函数(这是我使用它的方式),那么返回undefined实际上对我来说更加正确。 - Andrew Hedges
8
我已经给了你一个赞,但我对“简洁”有些困惑... - kennebec
2
试图使JavaScript源代码简洁似乎只会导致混淆。它应该尽可能清晰 - 这个问题的存在非常明显,JS代码需要更清晰!可以通过构建过程中引用的缩小器之一来传递它。 - intuited
@intuited 对的。就像我说的,这可能是一个无用的微观优化。话虽如此,缩小器不会执行此操作,因为它会改变函数的行为并可能导致代码崩溃。 - Peter Olson

53
我总是会回到Ben Alman的IIFE文章来解决这类问题。在我看来,这是最权威的。
以下是这篇文章的核心内容:
// Either of the following two patterns can be used to immediately invoke
// a function expression, utilizing the function's execution context to
// create "privacy."

(function(){ /* code */ }()); // Crockford recommends this one
(function(){ /* code */ })(); // But this one works just as well

// Because the point of the parens or coercing operators is to disambiguate
// between function expressions and function declarations, they can be
// omitted when the parser already expects an expression (but please see the
// "important note" below).

var i = function(){ return 10; }();
true && function(){ /* code */ }();
0, function(){ /* code */ }();

// If you don't care about the return value, or the possibility of making
// your code slightly harder to read, you can save a byte by just prefixing
// the function with a unary operator.

!function(){ /* code */ }();
~function(){ /* code */ }();
-function(){ /* code */ }();
+function(){ /* code */ }();

// Here's another variation, from @kuvos - I'm not sure of the performance
// implications, if any, of using the `new` keyword, but it works.
// http://twitter.com/kuvos/status/18209252090847232

new function(){ /* code */ }
new function(){ /* code */ }() // Only need parens if passing arguments

哦,不错。我之前没看到过。谢谢! - Andrew Hedges
27
哎呀,JavaScript 真是一团糟。 - BlueRaja - Danny Pflughoeft
8
JS并不是一团糟,实际上它非常强大——只是在某些地方的语法不太美观。 ;) - Brian Arnold Sinclair
请查看http://coffeescript.org,它可以简化语法但保留强大的功能。 - Callum Rogers
5
有了所有需要记住的额外语法(例如:yes、true、on、do、false、no、false、off),我认为CoffeeScript比JavaScript更加复杂。尽管CoffeeScript的字符数更少,但它并不更简单。https://gist.github.com/1248911 - Lime
2
它确实可以节省一个字节,但我认为这也可能是审美问题。我不认为 ! 很好看,但我认为它比嵌套的 ((){}()) 结构要少一些丑陋感。 - Chris Burt-Brown

14

看起来关键的是,你基本上是防止解析器将函数解释为函数声明,而是将其解释为匿名函数表达式。

使用括号来分组表达式或使用!来否定返回值都只是改变解析的技巧。然后立即通过以下括号调用它。在这方面,任何一个形式都具有相同的净效应,假设没有显式返回值:

(function(){ /* ... */ })(); // Arguably most common form, => undefined
(function(){ /* ... */ }()); // Crockford-approved version, => undefined
!function(){ /* ... */ }();  // Negates the return, so => true
+function(){ /* ... */ }();  // Attempts numeric conversion of undefined, => NaN
~function(){ /* ... */ }();  // Bitwise NOT, => -1
如果您没有捕获返回值,那么没有什么重大的区别。有人可能会认为 ~ 可能是一个更快的操作,因为它只是翻转位,或者 ! 是一个更快的操作,因为它是一个真/假检查并返回否定。

然而,大多数人使用此模式的方式是尝试打破一个新的作用域级别以保持清洁。所有形式都可以使用。后一种形式很受欢迎,因为它们引入了一个额外的(通常是不必要的)操作,但节省每个额外的字节都有帮助。

Ben Alman在这个主题上有一个很棒的介绍:http://benalman.com/news/2010/11/immediately-invoked-function-expression/


4

第一个“模式”调用匿名函数(并具有其返回值的结果),而第二个则调用匿名函数并否定其结果。

这就是你在问什么吗?它们做相同的事情。


4

这几乎只是样式上的偏好,除了 ! 提供函数返回(即返回 true,来自于 !undefined)之外。

此外,它少一个字符。


1

在第一个案例中,您使用( )来包装您想要执行的对象以及下一组(),而在下一个案例中,您使用一个接受一个参数的运算符(否定运算符!),并且您使其隐式地用( )括起其参数(函数),因此您实际上得到!(function(){}()),执行函数并否定它返回的结果。这也可以使用相同的原则用于-、+、~,因为所有这些运算符都只接受一个参数。

!function () { /* presumably the same magic happens */ }()
-function () { /* presumably the same magic happens */ }()
+function () { /* presumably the same magic happens */ }()
~function () { /* presumably the same magic happens */ }()

为什么你想要这样做呢?我猜这是个人喜好,或者如果你有一个很大的 .JS 文件并且想要在每个匿名函数调用中节省 1 个字符... :D


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