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

391

我阅读了一些有关闭包的帖子,发现到处都有这个东西,但是没有清晰的解释它是如何工作的 - 每次我只被告知使用它...

// 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)})

那么一个新的未命名函数将会被创建,例如 function ""(msg) ...

但为什么这样不起作用呢?

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

为什么它需要在同一行上?

您能否指出一些帖子或给我解释一下?


2
在其他编程语言中,这些被称为函数指针或委托,如果你想深入了解涉及的底层结构。 - Chris Moschini
17
第一行有一个分号。 - Oliver Ni
既然你知道它是如何工作的...就不要使用它。我们应该停止编写匿名函数。只需再加上几个字符,我们就可以给我们的函数一个真正的名称,并使调试Javascript代码变得更加容易! - Stijn de Witt
1
(function (msg){alert(msg)})('SO'); 这行代码可以独立运行,与你之前发布的另一个匿名函数没有任何关系。它们是两个完全独立的匿名函数。由于匿名函数没有名称且无法在之后引用,因此必须立即调用匿名函数。 - Octopus
19个回答

384

在函数定义后面去掉分号。

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

以上应该可以工作。

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

我在这篇帖子中讨论了这种模式:

jQuery和$问题

编辑:

如果您查看ECMA脚本规范,有3种定义函数的方式。(第98页,第13节函数定义)

1. 使用Function构造函数

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 Script规范:

FunctionDeclaration : function Identifier ( FormalParameterListopt ){ FunctionBody }

FunctionExpression : function Identifieropt ( FormalParameterListopt ){ FunctionBody }

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

这就意味着以下的写法是有效的:

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
就像我所写的那样,分号终止了匿名函数定义。由于它没有名称(显然是匿名的!),你将无法再调用它。如果不加分号,函数仍然可以执行。 - SolutionYogi
1
Nosredna,当涉及添加分号时,JS的行为有点随意。请阅读这篇详细的文章:http://blog.boyet.com/blog/javascriptlessons/javascript-for-c-programmers-magic-semicolons/ - SolutionYogi
还有一种调用匿名函数的方式:(function(msg) { alert(msg); }('SO')); //弹出SO。 - Oleksandr_DJ
@chharvey:将函数表达式传递给alert并没有太大的价值。但在jQuery的情况下,有许多情况下这是有用的。你的addClass示例对我来说没有太多意义,但函数on接受一个监听器,它是一个回调函数。on知道它将得到一个函数,并且会在恰当的时间调用它。回调函数主要用于异步操作(例如ajax请求)或事件处理。 - Stijn de Witt
显示剩余11条评论

133

这被称为自执行函数。

当你调用 (function(){}) 时,你返回的是一个函数对象。当你在它后面添加 () 时,它就会被调用,并且其主体中的任何内容都将被执行。分号;表示语句的结束,这就是第二次调用失败的原因。


我认为说函数体将被“evaled”是不正确的。它执行方式与任何其他函数相同。因为它是匿名的,所以你要么将引用保存在某个地方,要么立即执行它。 - SolutionYogi
16
个人而言,我甚至不喜欢“自调用函数”这个术语。并不是函数在调用自身,而是程序员写了那些括号来调用它。 - SolutionYogi
答案中引用的链接已经失效。 - I. J. Kennedy
此外,您可以将函数传递到自调用函数中,这真的很方便,不仅限于变量参数。分号告诉JS执行(结束)匿名函数。 - Felix
2
不错的文章链接。它指出了为什么有人会使用这个引用:“为了保护全局对象,所有的JavaScript应用程序都应该在一个自调用函数内编写。这将创建一个应用程序作用域,在其中可以创建变量,而不必担心它们与其他应用程序发生冲突。”并且还指出:“一旦函数终止,变量就被丢弃了,全局对象保持不变。” - yeahdixon
显示剩余4条评论

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。第一行声明了一个无名函数并将其分组,但没有调用它。第二行分组了一个字符串。两者都没有做任何事情。(文森特的第一个示例。)

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

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

但是
(foo)
(msg); //works

6
谢谢。你的例子非常清晰。我不知道在JavaScript中括号可以改变代码的含义。我来自Java背景,所以每天使用JavaScript时我都会学到一些新的(通常是意料之外的)东西。 - hotshot309
5
谢谢你逐步解释,这比我看到的其他任何解释都好得多。+1 - Wk_of_Angmar
2
这是一个重要的顿悟时刻- 感谢您用替换进行说明。+100 - FredTheWebGuy
1
我读过的有关匿名函数的最好解释之一。非常感谢! - Teknotica

24

匿名函数不是一个名字为空的函数,它只是没有名字的函数。

和 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"); }'时,它会发生什么?它会被垃圾回收吗? - palig
2
函数(){ alert("plop"); }指令只是分配函数,但不执行它也不将其绑定到变量。由于创建的函数未绑定到任何变量,因此它将很快被垃圾回收。 - Vincent Robert
这个SO线程超出了我们正在讨论的范围,但它解释了分离JavaScript命名空间的方法,并包括使用自调用函数的示例。 - hotshot309

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)();
有一个关于JavaScript函数语法的好的SO讨论

5

我理解提问者的问题是:

这个魔法是如何工作的:

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

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

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

原因是JavaScript括号,也就是()不能包含语句,当解析程序遇到函数关键字时,它知道将其解析为函数表达式而不是函数声明。
来源:博客文章立即调用的函数表达式(IIFE)

4

无括号示例:

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

(这是唯一真正使用void的方式,据我所知)

或者

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
哎呀,我的错。"* 2)如果Type(ref)不是引用,则返回true。*" 它只会为实际无法解析的引用引发错误。 - Bergi

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

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