为什么JavaScript IIFE需要括号?

14

我正在学习JavaScript中的IIFE,到目前为止已经理解了概念,但是我想知道外部圆括号的作用。具体来说,它们为什么是必需的?例如:

(function() {var msg='I love JavaScript'; console.log(msg);}());

运作良好,但是

function() {var msg='I love JavaScript'; console.log(msg);}();

产生语法错误。为什么?有很多关于IIFE的讨论,但我没有看到清晰的解释为什么需要括号。


4
简言之,就是歧义。我记不清确切术语了,但 function() {...} 是一个声明,而 (function() {...}) 是一个表达式,这个表达式可以被调用,也就是说 () 能够起作用。 - Niet the Dark Absol
2
FYI,这个也有效〜 +function() { ... }() 以及 !function() { ... }() - Phil
一个是函数表达式,解释器会尝试对其进行求值。另一个是函数定义,解释器只会将函数定义添加到当前作用域中。括号使其成为函数表达式,这正是你想要的。为什么它会以这种方式工作,是因为语言语法的细节。 - jfriend00
@Phil,这是正确的,因为+和!运算符将语句标记为表达式 :) - Allan Chua
这些评论很好地揭示了我的困惑核心 - 如果 function(){...} 是一个声明,那么 function(){...}() 不就是一个表达式吗?还是因为 function(){...}() 可以返回一个声明,所以外部括号是必要的,以消除歧义,正如 @Neit 所评论的那样? - buttonsrtoys
3个回答

20

在JavaScript中创建函数有两种方式(好吧,其实是三种,但我们忽略 new Function())。您可以编写函数声明或编写函数表达式。

函数声明本身是一个语句,并且语句本身不返回值(让我们忽略调试控制台或Node.js REPL 打印语句的返回值)。然而,函数表达式是一个真正的表达式,在JavaScript中表达式会返回立即可用的值。

现在,您可能已经看到有人说以下内容是函数表达式:

var x = function () {};

可能很容易得出结论,即这种语法:

function () {};

是使其成为表达式的关键。但这是错误的。上面的语法使其成为匿名函数。而匿名函数可以是声明或表达式。使其成为表达式的是这个语法:

var x = ...
那就是说,等号右侧的所有内容都是表达式。在编程语言中,使用表达式可以更轻松地编写数学公式。因此,通常任何需要处理数学的地方都是一个表达式。
JavaScript中表达式的一些形式包括:
- 等号操作符右侧的所有内容 - 花括号()中不是函数调用的部分 - 数学运算符(+-*/)右侧的所有内容 - 三元运算符.. ? .. : ..中的所有参数
当您编写时:
function () {}

这是一个声明,不返回值(即声明的函数)。因此,试图调用非结果会导致错误。

但是当你写:

(function () {})

它是一个表达式,返回一个值(已声明的函数),该函数可以立即使用(例如可以调用或分配)。

请注意上面算作表达式的规则。由此可知,花括号不是构建IIFE的唯一方法。以下是构建IIFE的有效方式(因为我们编写函数表达式):

tmp=function(){}()

+function(){}()

-function(){}()

0/function(){}()

0*function(){}()

0?0:function(){}()

(function(){}())

(function(){})()

你可能会在第三方库中看到上述非标准形式之一(特别是+版本),因为它们想节省一个字节。但我强烈建议您只使用大括号形式(任何一种都可以),因为它们被其他程序员广泛认可为IIFE。


4

1
"必须" 是一个有点强烈的词语。有其他方式来描述表达式。 - Phil
调用运算符也适用于声明,因为声明也会给出函数引用。 - Rahul Jain

3

这将是一个冗长的回答,但会为您提供必要的背景知识。在JavaScript中,函数有两种定义方式:

一种是函数定义(经典类型)

function foo() {
  //why do we always use
}

并且还有更为晦涩难懂的类型,即函数表达式

var bar = function() {
  //foo and bar
};

本质上,在执行时发生了相同的事情。创建函数对象,分配内存,并将标识符绑定到函数。不同之处在于语法。前者本身是一个声明新函数的语句,后者是一个表达式。

函数表达式使我们能够在任何需要普通表达式的地方插入函数。这为匿名函数和回调函数提供了便利。例如

setTimeout(500, function() {
  //for examples
});

在这里,匿名函数将在setTimeout命令执行时执行。然而,如果我们想立即执行函数表达式,我们需要确保语法被识别为表达式,否则我们就会产生歧义,无法确定我们是否意味着函数表达式或语句。

var fourteen = function sumOfSquares() {
  var value = 0;
  for (var i = 0; i < 4; i++)
    value += i * i;
  return value;
}();

sumOfSquares会立即被调用,因为它可以被识别为表达式。fourteen变成了14,并且sumOfSquares被垃圾回收。在你的例子中,分组运算符()将其内容强制转换为表达式,因此函数成为表达式,并可以立即被调用。

关于我第一个foo和bar例子之间的区别,有一件重要的事情需要注意,那就是hoisting。如果你不知道这是什么,可以快速搜索一下谷歌或其他搜索引擎来了解一下,但简单而又粗略的定义是:hoisting是JavaScript的行为,它将declarations(变量和函数)带到作用域的顶部。这些声明通常只提升标识符,而不是其初始化值,因此在赋值之前整个作用域都能看到变量/函数。

但是对于函数定义来说并非如此,这里整个声明都被提升,将在包含作用域内可见。

console.log("lose your " + function() {
  fiz(); //will execute fiz
  buzz(); //throws TypeError
  function fiz() {
    console.log("lose your scoping,");
  }
  var buzz = function() {
    console.log("and win forever");
  };
  return "sanity";
}()); //prints "lose your scoping, lose your sanity"

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