为什么需要在同一行上调用匿名函数?


374

我读了一些关于闭包的文章,到处都看到了,但是没有明确的解释它是如何工作的-每当我被告知要使用它时……:

// Create a new anonymous function, to use as a wrapper
(function(){
    // The variable that would, normally, be global
    var msg = "Thanks for visiting!";

    // Binding a new function to a global object
    window.onunload = function(){
        // Which uses the 'hidden' variable
        alert( msg );
    };
// Close off the anonymous function and execute it
})();

好的,我看到我们将创建一个新的匿名函数,然后执行它。所以之后,这个简单的代码应该可以工作了(并且可以):

(function (msg){alert(msg)})('SO');

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

(function (msg){alert(msg)})

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

但是,为什么这不起作用?

(function (msg){alert(msg)});
('SO');

为什么它需要在同一行?

您能给我指出一些帖子还是给我一个解释?


2
在其他语言中,如果您要查看所涉及的较低层结构,则将它们称为函数指针或委托。
克里斯·莫斯奇尼

17
你有一个 ; 在第一行
Oliver Ni

现在您知道了它是如何工作的...不要使用它。我们应该停止编写匿名函数。仅需几个字符,我们就可以给函数一个真实的名称,并使调试Javascript代码变得更加容易!
Stijn de Witt 2014年

1
该生产线(function (msg){alert(msg)})('SO');完全独立运行。它与您之前发布的其他匿名函数无关。这是两个完全独立的匿名函数。您必须立即调用一个匿名函数,因为它没有名称,以后也无法引用。
章鱼

Answers:


380

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

(function (msg){alert(msg)})
('SO');

以上应该工作。

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

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

jQuery和$问题

编辑:

如果您查看ECMA脚本规范,则可以通过3种方式定义函数。(第98页的第13节“功能定义”)

1.使用函数构造函数

var sum = new Function('a','b', 'return a + b;');
alert(sum(10, 20)); //alerts 30

2.使用函数声明。

function sum(a, b)
{
    return a + b;
}

alert(sum(10, 10)); //Alerts 20;

3.函数表达式

var sum = function(a, b) { return a + b; }

alert(sum(5, 5)); // alerts 10

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

根据ECMA脚本规范:

FunctionDeclaration:函数标识符(FormalParameterListopt){FunctionBody}

FunctionExpression:function Identifieropt(FormalParameterListopt){FunctionBody}

如果您注意到,“ identifier” 对于函数表达式是可选的。当您不提供标识符时,您将创建一个匿名函数。这并不意味着您无法指定标识符。

这意味着跟随是有效的。

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

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

var test1 = function test2() { alert(typeof test2); }

alert(typeof(test2)); //alerts 'undefined', surprise! 

test1(); //alerts 'function' because test2 is a function.

现场演示

比较一下

 function test1() { alert(typeof test1) };

 alert(typeof test1); //alerts 'function'

 test1(); //alerts 'function'

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

当您有类似的代码时,

    function(msg) { alert(msg); }

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

    (function(msg) { alert(msg); })('SO'); //alerts SO.

1
是的,但是为什么呢?为什么需要作为内联?无论我将使用多少空白。
palig

9
如我所写,分号终止了匿名函数定义。由于它没有名称(它是匿名duh!),因此您将无法再对其进行调用。如果您不输入分号,则函数仍可以执行。
SolutionYogi

我以为自动分号插入会在这种情况下放入分号,但事实并非如此。所以你是对的。
Nosredna

1
Nosredna,JS在添加分号时表现得有些随意。阅读此详细文章:blog.boyet.com/blog/javascriptlessons/…–
SolutionYogi

是的,我看到了(function(msg){alert(msg)})('SO'); 作品。我只是问为什么它起作用。指定的位置或此类型的JS功能。因此,一旦我打电话给我:(function(msg){alert(msg)})函数会发生什么?会GC吗?
palig

129

这称为自调用函数。

调用时您正在做的(function(){})是返回一个函数对象。当您追加()它时,它会被调用,并且体内的所有东西都会被执行。该;表示语句的结束,这就是为什么第二次调用失败。


好的,我知道了,所以这只是一些特殊的JS语法,对吗?最喜欢这个解释!简单和简短:)
palig

我认为说身体将被“逃避”是不正确的。它的执行与其他任何函数一样。因为它是匿名的,所以您可以将引用保存在某个地方或立即执行它。
SolutionYogi

16
就个人而言,我什至不喜欢“自我调用功能”一词。并不是说函数本身正在调用。程序员写了这些括号来调用它。
解决方案

它不是“特殊语法”,而是最特别的。实际上,“函数名(args){块}”形式更为“特殊”。它实际上是不必要的糖。但是,这实际上是使事情发生的原因。
jrockway

2
很好的文章链接。它指出了为什么有人会使用此引号:“为了保护全局对象,所有JavaScript应用程序都应在自调用函数中编写。这将创建一个应用程序范围,在其中可以创建变量而不必担心它们会发生冲突与其他应用程序。” 还应注意:“一旦函数终止,变量将被丢弃,并且全局对象保持不变。”
yeahdixon

94

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

这是您的基本声明函数。

例如 1:

var message = 'SO';

function foo(msg) {
    alert(msg);
}

foo(message);

函数是对象,可以分组。因此,让我们在函数周围添加括号。

例如 2:

var message = 'SO';

function foo(msg) {  //declares foo
    alert(msg);
}

(foo)(message);     // calls foo

现在,无需声明和立即调用相同的函数,我们可以在调用它时使用基本替换来声明它。

例如 3。

var message = 'SO';

(function foo(msg) {
    alert(msg);
})(message);          // declares & calls foo

最后,我们不需要该多余的foo,因为我们没有使用名称来调用它!函数可以是匿名的。

例如 4。

var message = 'SO';

(function (msg) {   // remove unnecessary reference to foo
    alert(msg);
})(message);

要回答您的问题,请参考示例2。第一行声明了一些无名函数并将其分组,但没有调用它。第二行将字符串分组。两者都不做。(Vincent的第一个示例。)

(function (msg){alert(msg)});  
('SO');                       // nothing.

(foo); 
(msg); //Still nothing.

(foo)
(msg); //works

6
谢谢。您的例子很清楚。我没有意识到JavaScript中的括号会以这种方式更改代码的含义。我来自Java背景,因此几乎每天使用JavaScript都会学到一些新知识(常常是意料之外的)。
2011年

5
感谢您一步一步地做,这比我见过的任何其他解释都好。+1
Wk_of_Angmar

2
此处是主要的AHA时刻-感谢您用替换方式进行说明。+100
FredTheWebGuy 2013年

1
我已阅读有关匿名函数的最好的解释之一。非常感谢!
Teknotica 2014年

23

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

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

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

这是一个函数和一个数字,不受限制,它们什么也不做,永远不能使用:

function(){ alert("plop"); }
2;

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

var f = function(){ alert("plop"); }
var n = 2;

您还可以使用语法糖将函数绑定到变量:

function f(){ alert("plop"); }
var n = 2;

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

(function(){ alert("plop"); })(); // will display "plop"
alert(2 + 3); // will display 5

在这里,我的函数和我的数字未绑定到变量,但仍可以使用。

这样说,看起来自调用功能没有实际价值。但是您必须记住,JavaScript作用域分隔符是函数而不是块({})。

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


好贴。然后,“ function(){alert(“ plop”))会发生什么;}'何时执行?会GC吗?
palig

2
function(){alert(“ plop”); }指令仅分配函数,但不执行该函数,也不将其绑定到变量。由于创建的函数未绑定到任何变量,因此它将被快速GC。
文森特·罗伯特

这个SO线程超出了我们在此讨论的范围,但是它解释了分隔JavaScript名称空间的方法-并包括使用自调用函数的示例。
2011年

19

这就是JavaScript的工作方式。您可以声明一个命名函数:

function foo(msg){
   alert(msg);
}

并称之为:

foo("Hi!");

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

var foo = function (msg) {
    alert(msg);
}

并调用:

foo("Hi!");

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

(function(msg){
   alert(msg);
 })("Hi!");

函数也可以返回函数:

function make_foo() {
    return function(msg){ alert(msg) };
}

(make_foo())("Hi!");

make_foo由所返回的每个函数都将关闭在主体中用“ var”定义的任何变量是毫无价值的make_foo。这是一个闭包,这意味着一个函数对值所做的任何更改将对另一个函数可见。

如果需要,这使您可以封装信息:

function make_greeter(msg){
    return function() { alert(msg) };
}

var hello = make_greeter("Hello!");

hello();

这几乎是除Java之外的每种编程语言的工作方式。


8

您显示的代码,

(function (msg){alert(msg)});
('SO');

两个语句组成。第一个是产生函数对象的表达式(由于未保存,因此将对其进行垃圾回收)。第二个是产生字符串的表达式。要将函数应用于字符串,您需要在创建函数时将字符串作为参数传递给函数(也在上面显示),或者需要将函数实际存储在变量中,以便您可以在以后的空闲时间应用它。像这样:

var f = (function (msg){alert(msg)});
f('SO');

请注意,通过在变量中存储匿名函数(lambda函数),可以有效地为其命名。因此,您也可以定义一个常规函数:

function f(msg) {alert(msg)};
f('SO');

7

总结以前的评论:

function() {
  alert("hello");
}();

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

(function() {
  alert("hello");
})();

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

一个很好的SO讨论JavaScript函数的语法


3

这个答案与问题并不严格相关,但是您可能有兴趣发现这种语法功能不是特定于函数的。例如,我们总是可以做这样的事情:

alert(
    {foo: "I am foo", bar: "I am bar"}.foo
); // alerts "I am foo"

与功能有关。由于它们是从Function.prototype继承的对象,因此我们可以执行以下操作:

Function.prototype.foo = function () {
    return function () {
        alert("foo");
    };
};

var bar = (function () {}).foo();

bar(); // alerts foo

而且您知道,为了执行它们,我们甚至不必用括号将函数括起来。无论如何,只要我们尝试将结果分配给变量即可。

var x = function () {} (); // this function is executed but does nothing

function () {} (); // syntax error

一旦声明了函数,您可能要做的另一件事是调用函数上的new运算符并获得一个对象。以下是等效的:

var obj = new function () {
    this.foo = "bar";
};

var obj = {
    foo : "bar"
};

3

JavaScript函数还有另外一个属性。如果要递归调用相同的匿名函数。

(function forInternalOnly(){

  //you can use forInternalOnly to call this anonymous function
  /// forInternalOnly can be used inside function only, like
  var result = forInternalOnly();
})();

//this will not work
forInternalOnly();// no such a method exist

2
+1添加了一个小样本,这样更清晰:-)第一次阅读时,我必须重新阅读4次。
xanatos 2011年

3

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

这个魔术是如何工作的:

(function(){}) ('input')   // Used in his example

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

(function(){}('input') )

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

来源:博客文章立即调用函数表达式(IIFE)


3

不带括号的示例:

void function (msg) { alert(msg); }
('SO');

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

要么

var a = function (msg) { alert(msg); }
('SO');

要么

!function (msg) { alert(msg); }
('SO');

也可以 在void导致该表达式评估,以及分配和爆炸。随着最后一部作品~+-deletetypeof,一些一元运算符的(void是一个为好)。由于需要变量++--因此无法工作是有原因的。

不需要换行。


@Bergi在ie11上delete工作。即使'use strict';。这也有效:delete (3 + 4);
Nina Scholz 2015年

糟糕,我的错。“ 2)如果Type(ref)不是Reference,则返回true。 ”它仅对无法解析的实际引用抛出错误。
Bergi 2015年

1

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

(function () {
    return ( 10 + 20 );
})();

彼得·米肖(Peter Michaux)讨论了一对重要括号中的区别。

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

看到:

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

0

换个角度来看

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

var foo = function(msg){
 alert(msg);
}

然后,您将其称为:

foo ('Few');

因为foo = function(msg){alert(msg);},所以您可以将foo替换为:

function(msg){
 alert(msg);
} ('Few');

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

(function(msg){
 alert(msg);
}) ('Few');

这样,对我来说很容易理解。


0

当您这样做时:

(function (msg){alert(msg)});
('SO');

('SO')由于分号,您之前已结束该函数。如果您只写:

(function (msg){alert(msg)})
('SO');

会的。

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


0

它不起作用的简单原因不是因为;指示了匿名函数的结束。这是因为,如果没有在()函数调用的末尾,则它不是函数调用。那是,

function help() {return true;}

如果您调用result = help();这是对函数的调用,则将返回true。

如果你打电话 result = help;这不是通话。这是一种将帮助视为要分配给结果的数据的任务。

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

(function (msg) { /* Code here */ });

然后尝试仅使用括号在另一条语句中调用它...显然是因为该函数没有名称,但这将无法工作:

('SO');

解释器将第二行上的括号视为新的指令/语句,因此即使您这样做,它也无法正常工作:

(function (msg){/*code here*/});('SO');

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

(function (msg){/*code here*/})        // This space is ignored by the interpreter
('SO');

结论:函数调用不是()最后没有结束的函数调用,除非在特定条件下(例如被另一个函数调用),也就是说,即使不包括括号,onload ='help'也会执行帮助函数。我相信setTimeout和setInterval也允许这种类型的函数调用,而且我也相信解释器无论如何都会在后台添加括号,这使我们回到“函数调用不是没有括号的函数调用”。


我不明白为什么这会收到这么多的反对票。我认为这是可以接受的答案?:/
Daniel Cheung)

0
(function (msg){alert(msg)})
('SO');

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

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

如果放在;第一行,则编译器将其视为两条不同的行。因此,您无法获得与上述相同的结果。

也可以写成:

(function (msg){alert(msg)}('SO'));

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


据我所知,JavaScript不会“编译”
Daniel Cheung

0
  1. 匿名函数是在运行时动态声明的函数。它们之所以称为匿名函数,是因为它们的命名方式与普通函数不同。

    匿名函数是使用函数运算符而不是函数声明来声明的。您可以在可以放置表达式的有效位置使用函数运算符创建一个新函数。例如,您可以声明一个新函数作为函数调用的参数或分配另一个对象的属性。

    这是命名函数的典型示例:

    function flyToTheMoon(){alert(“ Zoom!Zoom!Zoom!”); } flyToTheMoon(); 这是创建为匿名函数的相同示例:

    var flyToTheMoon = function(){alert(“ Zoom!Zoom!Zoom!”); } flyToTheMoon();

    有关详细信息,请阅读此处:

    http://helephant.com/2008/08/23/javascript-anonymous-functions/


0

IIFE只是分隔函数并隐藏msg变量,以免“污染”全局名称空间。实际上,除非您要建立一个十亿美元的网站,否则请保持简单并做以下操作。

var msg = "later dude";
window.onunload = function(msg){
  alert( msg );
};

您可以msg使用显示模块模式对属性进行命名空间,例如:

var myScript = (function() {
    var pub = {};
    //myscript.msg
    pub.msg = "later dude";
    window.onunload = function(msg) {
        alert(msg);
    };
    //API
    return pub;
}());

-1

匿名函数意味着可以一次性完成,您可以在其中动态定义一个函数,以便它从您提供的输入中生成您的输出。除了您没有提供输入。相反,您在第二行(“ SO”)写了一些内容;-与该功能无关的独立声明。您期望什么?:)


并非100%正确。这也是一个匿名函数,可以重用:var foo = function() {};。其他一切都很好。
Felix Kling
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.