JavaScript中的初始化函数及其工作原理

57

我经常看到以下代码:

(function () {
  // init part
})();

但我始终无法理解它是如何工作的。我特别困惑于最后的括号。 请问有人能够用执行上下文(EC)和变量对象(VO)的术语来解释一下它是如何工作的吗?


2
你对于根据 ECMA-262 规范的“正式解释”感兴趣吗? - Christian C. Salvadó
1
阅读有关此结构的目的。查看非技术性的解释,还可以在这里找到。有关语法,请参见为什么需要括号它们应该放在哪里 - Bergi
7个回答

81
我通常向人们解释这一点是通过展示它与其他JavaScript模式的相似之处。
首先,您应该知道有两种声明函数的方式(实际上,至少有五种,但这是其中两个主要罪犯):
function foo() {/*code*/}

var foo = function() {/*code*/};
即使这种构造看起来很奇怪,但当您附加事件时,您可能会经常使用它:
window.onload=function(){/*code*/};
您应该注意到第二种形式与常规变量声明没有太大区别:
var bar = 5;
var baz = 'some string';
var foo = function() {/*code*/};

但在JavaScript中,您始终可以选择直接使用值或通过变量使用。如果bar5,那么下面的两个语句是等效的:

var myVal = bar * 100; // use 'bar'
var myVal = 5 * 100;   // don't use 'bar'

如果你可以单独使用 5,为什么不能单独使用 function() {\*code*\} 呢?事实上,你可以。这就叫做匿名函数(anonymous function)。所以这两个例子也是等价的:

var foo = function() {/*code*/}; // use 'foo'
foo();                           

(function(){/*code*/})();        // don't use 'foo' 

你应该看到的唯一区别是额外的括号。这是因为如果你以关键字function开头的行,解析器会认为你正在使用此答案顶部的第一个模式声明函数,并抛出语法错误异常。因此,将整个匿名函数包装在一对大括号中即可解决问题。
换句话说,以下三个语句是有效的:
5;                        // pointless and stupid
'some string';            // pointless and stupid
(function(){/*code*/})(); // wonderfully powerful

[2020年修订版]

我的之前的回答推荐了Douglas Crockford版本的括号包裹来实现这些“立即调用的匿名函数”。用户@RayLoveless在2012年建议使用现在展示的版本。当时,在ES6和箭头函数出现之前,没有明显的惯用法区别; 你只需要防止以function关键字开头的语句。事实上,有很多方法可以做到这一点。但是使用括号,这两个语句在语法和惯用法上是等效的:

( function() { /* code */}() );
( function() { /* code */} )();

但是用户@zentechinc下面的评论提醒我,箭头函数改变了所有这些。因此现在只有其中一个语句是正确的。

( () => { /* code */ }() ); // Syntax error
( () => { /* code */ } )();

为什么这很重要?实际上,这很容易证明。记住箭头函数有两种基本形式:

() => { return 5; };       // With a function body
() => { console.log(5); };

() => 5;                   // Or with a single expression
() => console.log(5);

如果没有圆括号包装第二种箭头函数,你最终会得到一个惯用的混乱:

() => 5();              // How do you invoke a 5?
() => console.log(5)(); // console.log does not return a function!

2
我通常向人们解释这个问题的方式是展示它与其他JavaScript模式的相似之处。这非常有帮助,所以感谢您花时间做到这一点 :) - NessDan
1
我认为你输入的是 (function(){/code/}());,而你应该输入的是 (function(){/code/})();。正确吗? - RayLoveless
我从未说过这是最好的方法。我的观点是,大多数中级JS开发人员已经非常熟悉匿名函数,即使他们没有意识到,而进一步运用这个想法,使自执行函数的模式变得不那么“神奇”。 - Andrew
2
@RayLoveless,对于箭头函数似乎不是这种情况 (() => {console.log('<---------------> HERE I AM! <--------------->')}()) 无法运行,而 (function () {console.log('<---------------> HERE I AM! <--------------->')})() 却可以正常工作。 虽然这是一个很古老的评论,但仍然有趣的是跟踪这些变化。 顺便说一下,谢谢你的回答。 - zentechinc
1
@zentechinc,实际上,这是一个很好的观点,也说明了我的括号放置评论在习惯用语上是不正确的。这在2010年并不明显。我会更新我的答案。 - Andrew
显示剩余3条评论

51

这种模式将创建一个新的执行上下文(EC),其中任何本地变量对象(VO)都将存在,并且在EC退出时也将死亡。唯一的例外是成为闭包的VO。

请注意,JavaScript没有魔术的“init”函数。您可能会将此模式与此类关联,因为大多数受人尊敬的JS库(jQuery,YUI等)都会这样做,以便它们不会比需要更多地污染全局NS。

演示:

var x = 1; // global VO
(function(){        
    var x = 2; // local VO
})();
x == 1; // global VO, unchanged by the local VO

第二组“括号”(实际上称为圆括号或一组括号)仅是为了调用其前面定义的函数表达式(由前一组括号定义)。


7
如果你能解释一下EC和VO是什么,让那些不理解的开发者明白,我会给你点赞的。但是你并没有给出完整的答案,而是让我自己去Google这些缩写。 - Mark Broadhurst
14
我没有定义 EC 和 VO,因为原始问题已经包含了这些定义,所以如果你阅读了问题,你应该已经知道它们的含义。此外,这两个页面可能会帮助您更好地理解 EC/VO(变量):1. http://jibbering.com/faq/ 2. http://jibbering.com/faq/notes/closures/ - ken

26

这段代码创建了一个匿名函数,然后立即运行它。类似于:

var temp = function() {
  // init part
}
temp();

这个结构的目的是为函数内部的代码创建一个范围。你可以在这个范围内声明变量和函数,它们将局限于该范围内。这样就不会混淆全局范围,从而最小化与其他脚本发生冲突的风险。


尊敬的各位,对于那些不断点赞这个答案的人,所提供的解决方案根本没有回答OP提出的任何问题。他问的是最后一组括号[sic]以及给定代码与EC和VO的关系... Guffa提供的看起来像是普通的匿名函数答案,没有涉及到OP提出的具体问题。 - ken
3
人们似乎喜欢我解释代码的方式,以及为什么要使用它。即使我没有使用你所用的确切术语,也不代表我的解释是错误的。 - Guffa
我从未说过它是“错误的”,我只是指出你的复制/粘贴答案没有考虑到OP所问的问题,仅此而已。 - ken
愚蠢的问题,但是“init part”是什么意思?术语“Init”让我感到困惑。 - PulledBull
@PulledBull: Init代表初始化。在这种情况下,它只是意味着代码在页面被解析时运行。由于这是最先发生的事情,因此它是初始化事物的好地方。 - Guffa
@Guffa 谢谢,但是当你将其用作方法时,比如 foo.init,它是什么意思? - PulledBull

14

我简直不敢相信没有人回答运营问题!

最后一组括号用于将参数传递给匿名函数。因此,以下示例创建一个函数,然后使用x=5和y=8运行它。

(function(x,y){
    //code here
})(5,8)

这可能看起来并不那么有用,但它有其存在的价值。我见过最常见的一个例子是

(function($){
    //code here
})(jQuery)

这会让jQuery处于兼容模式,但你可以在匿名函数内将其称为"$"。


1
这也可以通过缩短该特定变量的作用域链来提高性能。 - ken

7

感谢您在此线程中分享名称...非常有帮助。 - greg

2
简单来说,每当页面加载时,这对括号()中的函数将被默认调用。我们不需要调用该函数,它被称为匿名函数。
即:
(function(a,b){
//Do your code here
})(1,2);

这与...相同

var test = function(x,y) {
  // Do your code here
}
test(1,2);

1
它被称为立即调用函数表达式(IIFE)。主要与JavaScript闭包概念相关联。主要用途是在全局变量改变之前运行函数,以保留代码的预期行为。

这里是链接:https://zh.wikipedia.org/wiki/Immediately-invoked_function_expression - Luke

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