391

我读了一些关于闭包的帖子,到处都看到了这一点,但没有明确的解释它是如何工作的——每次我被告知使用它……:

//创建一个新的匿名函数,用作包装器(函数(){//通常为全局变量var msg=“感谢您的访问!”;//将新函数绑定到全局对象window.onunload=函数(){//它使用“隐藏”变量警报(消息);};//关闭匿名函数并执行它})();

好的,我看到我们将创建新的匿名函数,然后执行它。因此,在这之后,这个简单的代码应该可以工作(它确实可以):

(函数(消息){alert(消息)})('SO');

我的问题是这里发生了什么魔术?当我写道:

(函数(消息){alert(消息)})

然后将创建一个新的未命名函数,如函数“”(msg)。。。

但为什么这行不通呢?

(功能(消息){alert(消息)});(“SO”);

为什么它需要在同一条线上?

你能给我指出一些帖子或给我一个解释吗?

4
  • 2
    在其他语言中,如果您想查看所涉及的低级别结构,这些被称为函数指针或委托。 评论 2011年11月16日15:15
  • 17
    您有一个;在第一行 评论 2013年11月29日21:43
  • 现在你知道它是怎么工作的了。。。不要用它。我们应该停止编写匿名函数。只需再添加几个字符,我们就可以为函数取一个实名,并使调试Javascript代码变得更加容易! 评论 2014年5月14日7:55
  • 1
    这条线(函数(消息){alert(消息)})('SO');完全独立工作。它与你之前发布的另一个匿名函数无关。这是两个完全独立的匿名函数。您必须立即调用一个匿名函数,因为它没有名称,以后无法引用。
    – 章鱼
    评论 2015年3月25日17:46

19个答案19

重置为默认值
384

将分号放在函数定义之后。

(函数(消息){alert(消息)})(“SO”);

以上应该有效。

演示页面:https://jsfiddle.net/e7ooeq6m/

我在这篇文章中讨论过这种模式:

jQuery和$问题

编辑:

如果你看看ECMA脚本规范,有三种方法可以定义函数。(第98页,第13节功能定义)

1.使用Function构造函数

var sum=新函数('a','b','return a+b;');警报(总和(10,20))//警报30

2.使用函数声明。

函数和(a,b){返回a+b;}警报(总和(10,10))//警报20;

3.函数表达式

var sum=函数(a,b){返回a+b;}警报(总和(5,5));//警报10

所以你可能会问,声明和表达之间有什么区别?

根据ECMA脚本规范:

功能声明:函数标识符(FormalParameterListopt){FunctionBody}

函数表达式:函数Identifieropt(FormalParameterListopt){FunctionBody}

如果您注意到,“标识符”是可选择的用于函数表达式。当你没有给出标识符时,你就创建了一个匿名函数。这并不意味着你不能指定标识符。

这意味着以下内容有效。

var sum=函数mySum(a,b){return a+b;}

需要注意的重要一点是,只能在mySum函数体内部使用“mySum”,而不能在外部使用。请参见以下示例:

var test1=函数test2(){alert(typeof test2);}警报(类型(测试2))//警报“未定义”,惊喜!测试1()//警告“函数”,因为test2是一个函数。

现场演示

将此与

函数test1(){alert(typeoftest1)};警报(测试类型1)//警报“功能”测试1()//警报“功能”

有了这些知识,让我们尝试分析您的代码。

当你有这样的代码时,

函数(消息){alert(消息);}

您创建了一个函数表达式。您可以通过将此函数表达式包装在括号内来执行它。

(函数(消息){alert(消息);})('SO')//向SO发出警报。
16
  • 1
    是的,但为什么?为什么需要内联?无论我将使用多少空白。
    – 帕利格
    评论 2009年7月16日20:30
  • 9
    正如我所写的,分号终止了匿名函数定义。因为它没有名字(它是匿名的!),你将不能再叫它了。若并没有放置分号,那个么函数仍然可以执行。 评论 2009年7月16日20:32
  • 我原以为自动分号插入会在这种情况下插入分号,但事实并非如此。所以你是对的。 评论 2009年7月16日20:49
  • 1
    Nosredna,JS在添加分号时几乎没有任意行为。阅读这篇详细文章:blog.boyet.com/blog/javascriptlessons/… 评论 2009年7月16日20:49
  • 是的,我看到了(函数(消息){alert(消息)})('SO');作品。我只是问它为什么工作。这是在哪里指定的,或者这是什么样的JS特性。所以一旦我调用:(function(msg){alert(msg,}))函数会发生什么?它将被GC'ed?
    – 帕利格
    评论 2009年7月16日20:52
133

它被称为自调用函数。

你打电话时正在做什么(函数(){})正在返回函数对象。当您附加()对它来说,它被调用,主体中的任何内容都被执行。这个;表示语句的结束,这就是第二次调用失败的原因。

9
  • 啊,好吧,我明白了,这只是一些特殊的JS语法,对吧?最喜欢这个解释!简单而简短:)
    – 帕利格
    评论 2009年7月16日20:40
  • 我认为,说尸体将被“蒸发”是不正确的。它的执行方式与其他任何函数一样。因为它是匿名的,所以要么将引用保存在某个地方,要么立即执行它。 评论 2009年7月16日20:45
  • 16
    就个人而言,我甚至不喜欢“自调用函数”这个词。并不是函数在调用自身。程序员写了那些括号来调用它。 评论 2009年7月16日20:48
  • 它不是“特殊语法”,比其他任何东西都特殊。实际上,“函数名(args){BLOCK}”形式更“特殊”。它实际上是多余的糖;然而,这才是事情发生的真正原因。 评论 2009年7月16日21:19
  • 2
    这篇文章的链接不错。它指出了为什么有人会引用这句话:“为了保护全局对象,所有JavaScript应用程序都应该写在一个自我调用函数中。这将创建一个应用程序范围,在这个范围内可以创建变量,而不用担心它们与其他应用程序发生冲突。”并注意到“一旦函数终止,变量将被丢弃,全局对象保持不变。” 评论 2013年9月13日18:36
94

我感到困惑的一件事是,“()”是分组运算符。

这是您的基本声明函数。

示例1:

var消息='SO';函数foo(消息){警报(消息);}foo(消息);

函数是对象,可以分组。让我们把parens放在函数周围。

示例2:

var消息='SO';函数foo(msg){//声明foo警报(消息);}(foo)(消息);//调用foo

现在,我们可以使用基本替换来声明我们所调用的函数,而不是声明和直接调用同一个函数。

例3。

var消息='SO';(函数foo(消息){警报(消息);})(消息);//声明并调用foo

最后,我们不需要额外的foo,因为我们不使用名称来调用它!函数可以是匿名的。

例4。

var消息='SO';(函数(msg){//删除对foo的不必要引用警报(消息);})(消息);

要回答您的问题,请参阅示例2。第一行声明一些无名函数并对其进行分组,但不调用它。第二行对字符串进行分组。两者都不做任何事情。(文森特的第一个例子。)

(函数(消息){alert(消息)});(“SO”);//没有什么。(foo);(消息)//还是什么都没有。

但是

(foo)(消息)//作品
4
  • 6
    谢谢。你的例子很清楚。我不知道JavaScript中的括号会以这种方式改变代码的含义。我有Java背景,所以我几乎每天都会学习一些关于JavaScript的新知识(通常是意料之外的)。
    – 热点309
    评论 2011年7月21日3:03
  • 5
    谢谢你一步一步地做,这比我见过的任何其他解释都要好得多+1 评论 2012年12月25日2:12
  • 2
    这里是AHA的重要时刻,谢谢你用替换来说明+100 评论 2013年9月7日0:47
  • 1
    我读过的关于匿名函数的最好解释之一。谢谢! 评论 2014年8月9日11:20
24

匿名函数不是名为“”的函数。它只是一个没有名称的函数。

与JavaScript中的任何其他值一样,函数不需要创建名称。尽管像其他任何值一样,将其实际绑定到名称上要有用得多。

但与任何其他值一样,有时您希望在不将其绑定到名称的情况下使用它。这就是自我调用模式。

这里有一个函数和一个数字,没有绑定,它们什么也不做,永远不能使用:

function(){警报(“plop”);}2;

因此,我们必须将它们存储在变量中才能使用它们,就像其他任何值一样:

var f=函数(){alert(“plop”);}变量n=2;

您还可以使用synstatic sugar将函数绑定到变量:

函数f(){alert(“plop”);}变量n=2;

但是,如果不需要命名它们,并且会导致更多混淆和可读性降低,那么您可以立即使用它们。

(函数(){alert(“plop”);})();//将显示“扑通”警报(2+3);//将显示5

这里,我的函数和数字没有绑定到变量,但它们仍然可以使用。

这样说来,自荐功能似乎没有实际价值。但您必须记住,JavaScript范围分隔符是函数而不是块({})。

因此,自我调用函数实际上与C++、C#或Java块具有相同的含义。这意味着在内部创建的变量不会“泄漏”到范围之外。这在JavaScript中非常有用,以免污染全局范围。

  • 帖子不错。当我执行'function(){alert(“plop”);}'时,它会发生什么?它将被GC'ed?
    – 栅栏
    评论 2009年7月16日20:57
  • 2
    function(){alert(“plop”);}指令只分配函数,但不执行它,也不将它绑定到变量。由于创建的函数没有绑定到任何变量,因此它将快速GCed。 评论 2009年7月17日8:35
  • 此SO线程这超出了我们在这里讨论的范围,但它解释了分离JavaScript名称空间的方法,并包括使用自我调用函数的示例。
    – 热点309
    评论 2011年7月21日3:08
19

这就是JavaScript的工作原理。您可以声明命名函数:

函数foo(消息){警报(消息);}

并称之为:

foo(“嗨!”);

或者,您可以声明一个匿名函数:

var foo=函数(消息){警报(消息);}

并称之为:

foo(“嗨!”);

或者,您永远不能将函数绑定到名称:

(功能(消息){警报(消息);})(“嗨!”);

函数也可以返回函数:

函数make_foo(){返回函数(消息){alert(消息)};}(make_foo())(“你好!”);

make_foo将由返回的每个函数关闭make_foo。这是一个闭包,意味着一个函数对值所做的任何更改都将被另一个函数看到。

这使您可以封装信息,如果您愿意:

函数make_greeter(消息){返回函数(){alert(msg)};}var hello=make_greeter(“你好!”);你好();

除了Java之外,几乎所有编程语言都是这样工作的。

8

您显示的代码,

(函数(消息){alert(消息)});(“o”);

包括声明。第一个是一个表达式,它生成一个函数对象(然后将对其进行垃圾收集,因为它没有保存)。第二个是产生字符串的表达式。要将函数应用于字符串,您要么需要在创建函数时将字符串作为参数传递给函数(如上所示),要么需要将函数实际存储在变量中,以便以后可以随意应用它。像这样:

var f=(函数(消息){警报(消息)});f(“SO”);

请注意,通过在变量中存储匿名函数(lambda函数),您实际上是在给它命名。因此,您不妨定义一个正则函数:

函数f(msg){alert(msg)};f(“SO”);
7

总结之前的评论:

函数(){警报(“hello”);}();

如果未分配给变量,则会产生语法错误。代码被解析为函数语句(或定义),这会导致右括号语法错误。在函数部分周围添加括号会告诉解释器(和程序员)这是一个函数表达式(或调用),如

(函数(){警报(“hello”);})();

这是一个自我调查函数,意味着它是匿名创建的,并立即运行,因为调用发生在声明它的同一行。此自查函数使用熟悉的语法表示,以调用无参数函数,并在函数名称周围添加括号:(myFunction)();.

良好的SO讨论JavaScript函数语法.

5

我对提问者的问题的理解是:

这个魔术是如何运作的:

(function(){})('input')//在他的示例中使用

我可能错了。然而,人们熟悉的通常做法是:

(函数(){}('输入'))

原因是JavaScript将AKA括起来(),不能包含语句,当解析器遇到函数关键字时,它知道将其解析为函数表达式而不是函数声明。

来源:博客帖子即时调用函数表达式(IIFE)

4

不带括号的示例:

无效函数(消息){alert(消息);}(“SO”);

(这是void的唯一真正用途,afaik)

var a=函数(消息){alert(消息);}(“o”);

!函数(消息){alert(消息);}(“SO”);

也能工作。这个空隙导致表达式求值,以及赋值和bang。最后一个适用于~,+,-,删除,类型(共种),一些一元运算符(空隙也是一个)。当然不工作++,--因为变量的要求。

不需要换行符。

2
  • @ie11上的Bergi删除作品。即使有'使用严格';。这也很有效:删除(3+4); 评论 2015年4月23日20:02
  • 哎呀,我错了。"2) 如果Type(ref)不是Reference,则返回true。“它只为无法解析的实际引用抛出错误。
    – 贝吉
    评论 2015年4月23日20:30

这个答案与这个问题没有严格的关系,但您可能会感兴趣地发现,这种语法特性并不是函数特有的。例如,我们总是可以这样做:

警报({foo:“我是foo”,bar:“我是bar”}.foo); // 提示“我是foo”

与函数相关。由于它们是继承自Function.prototype的对象,因此我们可以执行以下操作:

Function.prototype.foo=函数(){返回函数(){警报(“foo”);};};var bar=(函数(){}).foo();bar();//警告foo

你知道,为了执行函数,我们甚至不需要用括号括起来。总之,只要我们尝试将结果赋给变量。

var x=函数(){}();//此函数已执行,但不执行任何操作函数(){}();//语法错误

您可以对函数做的另一件事是,在声明函数后立即调用新的运算符并获取对象。以下是等效的:

var obj=新函数(){this.foo=“bar”;};var对象={foo:“bar”};

JavaScript函数还有一个属性。若你们想递归调用相同的匿名函数。

(函数forInternalOnly(){//可以使用forInternalOnly调用此匿名函数///forInternalOnly只能在函数内部使用,如var result=forInternalOnly();})();//这行不通仅限内部();//不存在这样的方法
1
  • 2
    +1添加了一个小样本,以便它更清晰:-)我第一次阅读它时,我不得不重读4次。 评论 2011年10月17日6:25
2

它是一个自动执行的匿名函数。第一组括号包含要执行的表达式,第二组括号执行这些表达式。

(函数(){返回(10+20);})();

Peter Michaux讨论了一对重要的括号.

当试图从父命名空间中隐藏变量时,它是一个有用的构造。函数中的所有代码都包含在函数的私有范围内,这意味着它根本无法从函数外部访问,这使得它真正私有。

请参见:

  1. 闭包(计算机科学)
  2. JavaScript名称间距
  3. 重要的一对Javascript括号
1

另一种观点

首先,您可以声明一个匿名函数:

var foo=函数(消息){警报(消息);}

然后你称之为:

foo(“少数”);

因为foo=函数(消息){alert(消息);}这样你就可以替换foo公司作为:

函数(消息){警报(消息);}(“很少”);

但您应该将整个匿名函数包装在一对大括号内,以避免在解析时声明函数时出现语法错误。那么我们有,

(功能(消息){警报(消息);})(“很少”);

这样,我很容易理解。

1

当您这样做时:

(函数(消息){alert(消息)});(“SO”);

你之前已经结束了这个功能(“SO”)因为分号。如果你只写:

(函数(消息){alert(消息)})(“SO”);

它会起作用的。

工作示例:http://jsfiddle.net/oliverni/dbVjg/

1

它不起作用的简单原因不是因为;指示匿名函数的结束。因为没有()在函数调用结束时,它不是函数调用。那就是,

函数help(){return true;}

如果你打电话结果=帮助();这是对函数的调用,将返回true。

如果你打电话结果=帮助;这不是电话。这是一种将帮助视为要分配给结果的数据的赋值。

您所做的是通过添加分号来声明/实例化一个匿名函数,

(函数(消息){/*此处编码*/});

然后尝试在另一个语句中使用括号调用它。。。显然,因为函数没有名称,但这不起作用:

(“SO”);

口译员将第二行的括号视为新的指令/语句,因此它不起作用,即使您这样做:

(函数(消息){/*此处编码*/});(“SO”);

它仍然不起作用,但当您删除分号时,它会起作用,因为解释器会忽略空格和车厢,并将整个代码视为一条语句。

(函数(msg){/*此处的代码*/})//此空格被解释器忽略(“SO”);

结论:函数调用不是没有()最后,除非在特定条件下(例如被另一个函数调用),即onload='help'将执行help函数,即使没有包括括号。我认为setTimeout和setInterval也允许这种类型的函数调用,而且我还认为解释器在幕后添加了括号,这让我们回到了“函数调用不是没有括号的函数调用”。

1
  • 我不明白为什么这会得到这么多的反对票。我认为这是一个可以接受的答案/ 评论 2015年7月3日13:37
1
(函数(消息){alert(消息)})(“SO”);

这是使用匿名函数作为闭包的常见方法,许多JavaScript框架都使用这种方法。

编译代码时会自动调用此函数。

如果放置;在第一行,编译器将其视为两个不同的行。所以你不能得到与上面相同的结果。

这也可以写成:

(功能(消息){alert(消息)}('SO'));

有关更多详细信息,请查看JavaScript/匿名函数.

1
  • 据我所知,JavaScript不会“编译” 评论 2015年7月3日13:35
1

IIFE简单地划分了函数并隐藏了消息变量以避免“污染”全局命名空间。实际上,只要保持简单,并像下面这样做,除非你正在建设一个十亿美元的网站。

var msg=“稍后的伙计”;window.onunload=函数(消息){警报(消息);};

您可以命名您的消息属性使用揭示模块模式例如:

var myScript=(函数(){变量pub={};//myscript.msgpub.msg=“稍后的伙计”;window.onunload=函数(消息){警报(消息);};//美国石油学会返回酒吧;}());
1

匿名函数是在运行时。它们被称为匿名函数,因为它们不是以与普通函数相同的方式命名。

使用函数运算符声明匿名函数函数声明的。您可以使用函数运算符来在表达式有效的地方创建一个新函数。对于例如,可以将新函数声明为函数调用或分配另一个对象的属性。

下面是命名函数的典型示例:

函数flyToTheMoon(){提示(“缩放!缩放!缩放”);}飞到月球();

下面是创建为匿名函数的相同示例:

var flyToTheMoon=函数(){提示(“缩放!缩放!缩放”);}飞向月球();

有关详细信息,请阅读http://helephant.com/2008/08/23/javascript-anonymous函数/

-1

匿名函数是指一次性的交易,您可以动态定义一个函数,以便它从您提供的输入中生成输出。只是你没有提供输入。相反,你在第二行写了一些东西(“SO”);-与函数无关的独立语句。你期望什么

1
  • 不是100%正确。这也是一个匿名函数,可以重用:var foo=函数(){};不过,其他一切都很好。 评论 2013年4月24日6:20

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