Why are parentheses required around JavaScript IIFE?
我正在阅读JavaScript IIFE和迄今为止的理解概念,但我想知道外部括号。 具体来说,他们为什么需要? 例如,
1 | (function() {var msg='I love JavaScript'; console.log(msg);}()); |
效果很好,但是
1 | function() {var msg='I love JavaScript'; console.log(msg);}(); |
生成语法错误。 为什么? 关于IIFE的讨论很多,但我没有看到为什么需要括号的明确解释。
有两种方法可以在JavaScript中创建函数(好吧,3,但是让我们忽略
函数声明本身就是一个语句,语句本身不返回值(让我们也忽略调试控制台或Node.js REPL如何打印语句的返回值)。然而,函数表达式是一个正确的表达式,JavaScript中的表达式返回可以立即使用的值。
现在,您可能已经看到有人说以下是函数表达式:
1 | var x = function () {}; |
结论语法可能很诱人:
1 | function () {}; |
是什么让它成为一种表达。但那是错的。上面的语法是使它成为匿名函数的原因。匿名函数可以是声明或表达式。使它成为表达式的是这种语法:
1 | var x = ... |
也就是说,
JavaScript中的一些表达形式包括:
-
= 运算符右侧的所有内容 -
大括号
() 中不是函数调用大括号的东西 -
数学运算符右侧的所有内容(
+ ,- ,* ,/ ) -
三元运算符
.. ? .. : .. 的所有参数
当你写:
1 | function () {} |
它是一个声明,不返回值(声明的函数)。因此,尝试调用非结果是一个错误。
但是当你写:
1 | (function () {}) |
它是一个表达式并返回一个值(声明的函数),它可以立即使用(例如,可以被调用或者可以被赋值)。
请注意上面表达式的规则。由此可见,大括号不是你可以用来构建IIFE的唯一东西。下面是构建IIFE的有效方法(因为我们编写函数表达式):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | tmp=function(){}() +function(){}() -function(){}() 0/function(){}() 0*function(){}() 0?0:function(){}() (function(){}()) (function(){})() |
实际上,您可能会在第三方库中看到上述非标准表单之一(尤其是
包含在括号中的IIFE版本起作用,因为它将内部函数声明的声明标记为表达式。
http://benalman.com/news/2010/11/immediately-invoked-function-expression/
有关详细说明,请参阅:
高级JavaScript:为什么这个函数包含在括号中?
暗示:
调用运算符(())仅适用于表达式,而不适用于声明。
这将是一个冗长的答案,但会给你必要的背景。在JavaScript中,有两种方法可以定义函数:
功能定义(经典类)
1 2 3 | function foo() { //why do we always use } |
然后是更模糊的类型,一个函数表达式
1 2 3 | var bar = function() { //foo and bar }; |
从本质上讲,同样的事情正在执行中。创建一个函数对象,分配内存,并将标识符绑定到该函数。区别在于语法。前者本身是一个声明一个新函数的语句,后者是一个表达式。
函数表达式使我们能够在任何预期正常表达式的地方插入函数。这为匿名函数和回调提供了方便。举个例子
1 2 3 | setTimeout(500, function() { //for examples }); |
这里,只要setTimeout这样说,匿名函数就会执行。但是,如果我们想立即执行函数表达式,我们需要确保语法可以被识别为表达式,否则我们对于是否意味着函数表达式或语句都有歧义。
1 2 3 4 5 6 | var fourteen = function sumOfSquares() { var value = 0; for (var i = 0; i < 4; i++) value += i * i; return value; }(); |
这里立即调用
关于我的第一个foo和bar示例之间的区别的一个重要注意事项是吊装。如果您不知道它是什么,快速谷歌搜索或两个应该告诉您,但快速和脏的定义是,提升是JavaScript的行为,将声明(变量和函数)带到范围的顶部。这些声明通常只提升标识符而不是其初始化值,因此整个范围将能够在赋值之前查看变量/函数。
对于函数定义,情况并非如此,此处整个声明将被提升并在整个包含范围内可见。
1 2 3 4 5 6 7 8 9 10 11 | 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" |