57

我经常看到以下代码:

(函数(){//初始化部分})();

但我一直无法了解它是如何工作的。我觉得最后的括号特别令人困惑。有人能从执行上下文(EC)和变量对象(VO)的角度解释一下它是如何工作的吗?

2

7个答案7

重置为默认值
81

我通常向人们解释这一点的方式是展示它与其他JavaScript模式的相似性。

首先,您应该知道有两种方法可以声明函数(实际上,至少有五种,但这是两种主要原因):

函数foo(){/*code*/}

var foo=函数(){/*代码*/};

即使此结构看起来很奇怪,您可能在附加事件时一直使用它:

window.onload=函数(){/*代码*/};

您应该注意到,第二种形式与常规变量声明没有太大区别:

var bar=5;var baz='some string';var foo=函数(){/*代码*/};

但在JavaScript中,您总是可以在直接使用值或通过变量使用值之间进行选择。如果酒吧5,则接下来的两个语句等效:

var myVal=bar*100;//使用“bar”var myVal=5*100;//不要使用“bar”

如果你能用5就其本身而言,为什么你不能使用函数(){\*代码*\}也是靠自己吗?事实上,你可以。这被称为匿名函数。因此,这两个示例也是等效的:

var foo=函数(){/*代码*/};//使用“foo”foo();(函数(){/*代码*/})();//不要使用“foo”

您应该看到的唯一区别是在额外的括号中。这只是因为如果你用关键字开始一行功能,解析器会认为您正在使用此答案顶部的第一个模式声明函数,并抛出语法错误异常。因此,将整个匿名函数包装在一对大括号中,问题就解决了。

换言之,以下三种说法是有效的:

5;                        // 毫无意义和愚蠢'一些字符串';//毫无意义又愚蠢(函数(){/*代码*/})();//非常强大

【2020年EDIT】

我之前的答案建议使用道格拉斯·克罗克福德(Douglas Crockford)的parens-wrapping形式来实现这些“立即调用的匿名函数”。2012年,用户@RayLoveless建议使用现在显示的版本。当时,在ES6和箭头函数之前,没有明显的习惯用法差异;您只需阻止语句以功能关键字。事实上,有很多方法可以做到这一点。但使用parens,这两个语句在语法和习惯用法上是等价的:

(函数(){/*代码*/}());(函数(){/*代码*/})();

但下面用户@zentechinc的评论提醒我,箭头功能改变了这一切。所以现在只有一个说法是正确的。

(()=>{/*code*/}());//语法错误(()=>{/*code*/})();

这到底有什么关系?事实上,这很容易演示。记住,箭头函数可以有两种基本形式:

()=>{return 5;};//具有功能体()=>{console.log(5);};() => 5;                   // 或使用单个表达式()=>console.log(5);

如果没有parens包装第二种类型的箭头函数,您最终会得到一个惯用的混乱:

() => 5();              // 如何调用5?()=>console.log(5)();//console.log没有返回函数!
8
  • 2
    “我通常向人们解释这一点的方式是展示它与其他JavaScript模式的相似性。”这非常有用,所以感谢您抽出时间这么做:)
    – 奈斯丹
    评论 2012年7月12日18:23
  • 1
    我想你输入了(function(){/*code*/}());您的意思是键入(function(){/*code*/})();。对的? 评论 2012年12月11日4:57
  • 不,但没关系。道格拉斯·克罗克福德推荐了我使用的表格,而许多人推荐了你的表格。请参阅stackoverflow.com/questions/3783007/…。碰巧,父母的原因是你试图避免以功能(所以你可以很容易地做到!函数(){/*代码*/}()-函数(){/*代码*/}()我不喜欢克罗克福德推荐的所有东西,但我认为他严格的一致性比每个人都要做到的更好,而且他的衣领工具(JSLint)也需要这样。
    – 安得烈
    评论 2012年12月11日11:38
  • 2
    @RayLoveless对于fat arrow函数(()=>{console.log('<-------------->HERE I AM!<-------------->')}())无法运行,而(function(){console.log('<------------->HERE I AM!<----------------->'。顺便说一下,谢谢你的回答。 评论 2020年4月6日2:03
  • 1
    @事实上,zentechinc提出了一个很好的观点,这表明我对parens位置的评论在习惯用法上是不正确的。这在2010年并不明显。我会更新我的答案。
    – 安得烈
    评论 2020年4月7日7:45
51

该模式将创建一个新的执行上下文(EC),任何局部变量对象(VO)都将在其中生存,同样,当EC退出时也会死亡。此生唯一的例外是VO成为关闭.

请注意,JavaScript没有神奇的“init”函数。您可能会将此模式与之关联,因为大多数自省JS库(jQuery、YUI等)都会这样做,这样就不会对全局NS造成超出需要的污染。

演示:

var x=1;//全球VO(函数(){var x=2;//本地VO})();x==1;//全球VO,本地VO不变

第二组“括号”(实际上称为parens或一组括号)只是为了调用它前面的函数表达式(如前一组括号所定义)。

2
  • 7
    现在,如果你解释一下EC和VO对于那些不理解的开发人员来说是什么,那么我会给你一个提示,但不是完整的答案,而是一个我可能不得不使用谷歌缩略语的答案。 评论 2013年5月29日12:07
  • 14
    我没有定义EC和VO,因为最初的问题包含了这些的定义,所以如果你读了这个问题,你应该已经知道它们的含义;)此外,这两页可能有助于您更好地理解EC/VO(变量):1。jibbering.com/faq2jibbering.com/faq/notes/closures
    – 
    评论 2013年5月29日15:16
26

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

var temp=函数(){//初始化部分}温度();

此构造的目的是为函数内的代码创建一个范围。您可以在范围内声明变量和函数,这些变量和函数将是该范围的本地变量和函数。这样,它们就不会扰乱全局范围,从而将与其他脚本冲突的风险降至最低。

6
  • 尊敬的是,对于那些一直投票支持这个答案的人来说,给定的解决方案并没有回答OP提出的任何问题完全他询问了最后一组括号[原文如此],以及给定的代码与EC和VO之间的关系……Guffa提供的内容看起来像是普通的匿名函数答案,它没有解决OP提出的具体问题。
    – 
    评论 2010年11月18日18:47
  • @肯:人们似乎喜欢我如何解释代码的作用以及为什么使用它。仅仅因为我没有使用你使用的确切术语,这并没有错。 评论 2010年11月18日21:38
  • 我从来没有说过是/错/,我只是指出你的复制/粘贴答案没有考虑OP的要求,仅此而已。
    – 
    评论 2010年11月18日22:00
  • 愚蠢的问题,但“init part”是什么意思?术语“Init”让我困惑 评论 2016年8月25日22:58
  • @PulledBull:Init代表初始化/初始化。在这种情况下,它只是意味着代码在页面解析时运行。因为这是第一次发生,所以这是一个初始化事物的好地方。 评论 2016年8月27日12:29
14

我不敢相信没有人回答过行动问题!

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

(函数(x,y){//此处输入代码})(5,8)

这似乎没什么用处,但它有它的位置。我见过的最常见的是

(函数($){//此处输入代码})(jQuery)

这允许jQuery处于兼容模式,但您可以在匿名函数中将其称为“$”。

1
  • 1
    这还可以通过缩短范围链对于该特定变量。
    – 
    评论 2014年4月3日19:19
7

自调用匿名函数(SIAF)

即使DOM还没有完全准备好,自调用函数也会立即运行。

jQuery document.ready与自调用匿名函数

1
  • 谢谢你与这个帖子分享这个名字。。。非常有用
    – 格雷格
    评论 2014年4月7日22:05
2

简单地说,您可以理解,无论何时加载页面,通过第二对括号()函数都会调用default。我们不需要调用该函数。它被称为匿名函数。

(函数(a,b){//在此处执行代码})(1,2);

它与

var测试=函数(x,y){//在此处输入代码}试验(1,2);
1

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

1

你的答案

点击“发布您的答案”,即表示您同意我们的服务条款并确认您已阅读我们的隐私政策.

不是你想要的答案吗?浏览标记的其他问题问你自己的问题.