为什么 JavaScript IIFE 需要括号?

IT技术 javascript iife
2021-02-25 06:06:08

我正在阅读 JavaScript IIFE 和到目前为止的理解概念,但我想知道外面的括号。具体来说,为什么需要它们?例如,

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

效果很好,但是

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

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

3个回答

有两种方法可以在 JavaScript 中创建函数(好吧,3,但让我们忽略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。

括号中的 IIFE 版本有效,因为这将内部函数声明的声明标记为表达式。

http://benalman.com/news/2010/11/immediately-invoked-function-expression/

更详细的解释请看:

高级 JavaScript:为什么这个函数用括号括起来?

暗示:

调用运算符 (()) 仅适用于表达式,而不适用于声明。

调用运算符也使用声明,因为声明也会提供函数引用
2021-05-13 06:06:08
“必须”是一个有点强烈的术语。还有其他方法来描述表达式
2021-05-18 06:06:08

这将是一个冗长的答案,但会为您提供必要的背景知识。在 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;
}();

HeresumOfSquares被立即调用,因为它可以被识别为一个表达式。fourteen变成14并且 sumOfSquares 被垃圾收集。在您的示例中,分组运算符将()其内容强制转换为表达式,因此该函数是一个表达式,因此可以立即调用。

关于我的第一个 foo 和 bar 示例之间的区别,需要注意的一件重要事情是提升。如果你不知道它是什么,一两次快速的谷歌搜索应该会告诉你,但快速而肮脏的定义是提升是 JavaScript 将声明(变量和函数)带到作用域顶部的行为。这些声明通常只提升标识符而不是它的初始化值,因此整个范围将能够在分配值之前看到变量/函数。

对于函数定义,情况并非如此,这里整个声明都被提升并且在整个包含范围内都是可见的。

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"