JavaScript中的(function() { } )()构造是什么?

945

我想知道这是什么意思:

(function () {

})();

这是否基本意味着document.onload


23
顺便说一下,虽然你会看到人们称这个函数为“自我调用”,但这显然不是真的。术语“立即调用函数表达式(IIFE)”具有更高的准确性优势。 - AakashM
6
这篇文章很好地解释了这个概念,并且也是“立即调用函数表达式”一词的起源所在。http://benalman.com/news/2010/11/immediately-invoked-function-expression/ - jeremysawesome
2
关于这个结构的命名,请参考这里。阅读有关此结构的目的,以及技术解释(还可以在这里找到)。关于语法,请查看为什么需要括号它们应该放在哪里 - Bergi
https://dev59.com/KHE95IYBdhLWcg3wMa51 - Adriano
可能是JavaScript中自执行函数的目的是什么?的重复问题。 - Tobias Kienzler
28个回答

976

这是一个立即调用函数表达式,简称IIFE。它在创建后立即执行。

它与任何事件处理程序无关(例如document.onload)。
考虑第一对括号中的部分:(function(){})();... 它是一个常规函数表达式。然后看看最后一对括号(function(){})();,这通常添加到表达式以调用函数;在这种情况下,我们之前的表达式。

当尝试避免污染全局命名空间时,经常使用此模式,因为IIFE内使用的所有变量(如任何其他正常函数中)在其范围之外不可见。
这就是为什么您可能会将此结构与window.onload的事件处理程序混淆,因为它经常被用作此类操作:

(function(){
  // all your code here
  var foo = function() {};
  window.onload = foo;
  // ...
})();
// foo is unreachable here (it’s undefined)

Guffa提出的更正意见:

函数在创建后立即执行,而不是在解析后执行。整个脚本块在其中任何代码执行之前都会被解析。此外,解析代码并不意味着它会自动执行,例如如果IIFE在函数内部,则在调用函数之前不会执行。

更新 由于这是一个相当流行的主题,值得一提的是,IIFE也可以使用ES6的箭头函数编写(如Gajus评论中指出的那样):

((foo) => {
 // do something with foo here foo
})('foo value')

1
@gion_13 创造阶段和解析阶段有什么区别? - akantoword
2
@jlei 我的理解是,一个 JavaScript 程序的生命周期包括以下几个阶段:解析、创建/编译、执行。虽然实际的实现方式(以及命名方式 :)))可能因浏览器而异,但我们可以通过观察解析错误、提升和运行时错误来确定这些阶段在我们的代码中的存在。我个人没有找到太多关于这方面的资源,因为它太底层了,而且不是程序员可以控制的东西。你可以在这篇 SO 帖子中找到一些解释:https://dev59.com/N5Lea4cB1Zd3GeqP4ZkF#34562772 - gion_13
1
@Pankaj - 单独看,这甚至不是JS的语法正确(它是一个函数表达式,但不在表达式上下文中,因此被视为语法错误)。 - Quentin
1
需要注意的是,IFFE非常有用,因为它使用了全局作用域下的var关键字。因此,JS开发人员必须找到一种方法来“限制”代码中的变量。 - theProCoder
另一个优点是,我们可以在任何地方调用返回以退出代码块,否则这将变得很丑。 - Abhishek Choudhary
显示剩余2条评论

126

这只是一个匿名函数,在创建之后立即执行。

就像您将其分配给变量,然后立即使用它一样,只是没有变量:

var f = function () {
};
f();

在jQuery中有一个类似的结构,你可能正在考虑它:

$(function(){
});

这是绑定 ready 事件的简写形式:

$(document).ready(function(){
});

但是上述两个结构不是IIFE


92
最后两个不算是IIFE,因为它们在DOM准备就绪时被调用,而不是立即被调用。 - svvac
19
@ swordofpain:是的,没错,它们不是IIFE。 - Guffa
@swordofpain 考虑第二个片段;将函数转换为IIFE并在末尾添加(),是否有任何价值? - timebandit
在结尾处加分号是否必要? - FrenkyB
@FrenkyB 不是必须的,但是建议使用(在JavaScript中分号通常不是必需的,但这是一个好习惯)。每个语句都包含匿名函数,而不是函数声明。 - Ledivin
显示剩余2条评论

70

立即调用函数表达式(IIFE)立即调用函数。这意味着在定义完成后立即执行该函数。

另外还有三种常见的说法:

// Crockford's preference - parens on the inside
(function() {
  console.log('Welcome to the Internet. Please follow me.');
}());

//The OPs example, parentheses on the outside
(function() {
  console.log('Welcome to the Internet. Please follow me.');
})();

//Using the exclamation mark operator
//https://dev59.com/PG865IYBdhLWcg3wkPWD#5654929
!function() {
  console.log('Welcome to the Internet. Please follow me.');
}();
如果对其返回值没有特殊要求,那么我们可以这样写:

如果对其返回值没有特殊要求,则可以编写以下代码:

!function(){}();  // => true
~function(){}(); // => -1
+function(){}(); // => NaN
-function(){}();  // => NaN

或者,它可以是:

~(function(){})();
void function(){}();
true && function(){ /* code */ }();
15.0, function(){ /* code */ }();

你甚至可以写:

new function(){ /* code */ }
31.new function(){ /* code */ }() //If no parameters, the last () is not required

5
最后一个31.new是无效语法。 - cat
14
为什么表达同一个意思的方式这么多啊!!>_< 我不喜欢这种语言。 - Awesome_girl
9
获胜者是 ;(function(){}()); - Roko C. Buljan
1
@Awesome_girl:并不是有很多种写同一件事的方法;而是JS具有宽松的类型系统,其操作符可以对任何值类型进行操作。你可以执行 1-1,也可以轻松地执行 true - function() {}。只是一个东西(中缀减法运算符),但使用了不同甚至是毫无意义的操作数。 - user9274775
1
使用Crockford的函数(function(){}())相比其他函数有什么好处? - Robert
显示剩余3条评论

48

那个构造被称为立即调用函数表达式(IIFE),这意味着它会立即执行。可以将其视为当解释器到达该函数时自动调用该函数。

最常见的用法:

其最常见的用例之一是限制通过 var 创建的变量的范围。通过 var 创建的变量的作用域仅限于函数,因此这种构造(它是某些代码周围的函数包装器)将确保您的变量范围不会泄漏出该函数。

在以下示例中,count 将不可在立即调用的函数之外使用,即 count 的作用域不会泄露出该函数。如果尝试在立即调用的函数之外访问它,应该会收到一个 ReferenceError

(function () { 
    var count = 10;
})();
console.log(count);  // Reference Error: count is not defined

ES6替代方案(推荐)

在ES6中,我们现在可以使用letconst创建变量。它们都是块级作用域的(不像var是函数作用域的)。

因此,与上面提到的使用IIFE的复杂结构相比,您现在可以编写更简单的代码,以确保变量的作用域不会泄漏到您期望的块之外。

{ 
    let count = 10;
}
console.log(count);  // ReferenceError: count is not defined
在这个例子中,我们使用了let来定义count变量,这使得count的作用范围被限制在了我们用花括号{...}创建的代码块中。
我称之为“花括号监狱”。

17
我喜欢“卷发监狱”这个名称。也许它会流行起来 :) - gion_13

33

这意味着立即执行。

因此,如果我这样做:

var val = (function(){
     var a = 0;  // in the scope of this function
     return function(x){
         a += x;
         return a;
     };
})();

alert(val(10)); //10
alert(val(11)); //21

Fiddle: http://jsfiddle.net/maniator/LqvpQ/


第二个示例:

var val = (function(){
     return 13 + 5;
})();

alert(val); //18

3
我不明白那证明了什么,它是自我调用的吗? - Exitos
1
@Exitos 是因为它返回该函数。我会给出第二个例子。 - Naftali
very easy to understand +1 - Adiii

33
它声明了一个匿名函数,然后调用它:
(function (local_arg) {
   // anonymous function
   console.log(local_arg);
})(arg);

我猜“arguments”是指外部变量,被引用为“arg”,以在函数内的本地上下文中使用? - Dalibor
@Dalibor arguments特殊的; 我猜回答者只是颠倒了名称的位置。 - cat

18
(function () {
})();

这被称为IIFE(即时调用函数表达式),是著名的JavaScript设计模式之一,也是现代模块模式的核心。顾名思义,它在创建后立即执行。该模式创建了一个隔离或私有的执行范围。

ECMAScript 6之前的JavaScript使用词法作用域,因此IIFE用于模拟块级作用域。(随着ECMAScript 6引入let和const关键字,块级作用域成为可能。) 词法作用域问题的参考

使用IIFE模拟块级作用域

使用IIFE的性能优势在于能够将常用的全局对象(例如windowdocument等)作为参数传递,从而减少范围查询。 (记住,JavaScript会在本地范围内查找属性,并向上追溯链直到全局范围)。 因此,在本地范围内访问全局对象可减少查找时间,如下所示。

(function (globalObj) {
//Access the globalObj
})(window);

感谢您提供IIFE中第二个括号的gist,以便理解。同时,通过在定义中定义全局变量来澄清查找时间的好处,也非常感谢。 - Arsal

16

这是JavaScript中的立即调用函数表达式:

要理解JS中的IIFE,让我们来分解一下:

  1. 表达式:返回一个值的东西
    示例:在Chrome控制台中尝试以下操作。这些是JavaScript中的表达式。
a = 10 
output = 10 
(1+3) 
output = 4
  • 函数表达式:
    例子:
  • // Function Expression 
    var greet = function(name){
       return 'Namaste' + ' ' + name;
    }
    
    greet('Santosh');
    
    函数表达式的工作原理:
    - 当JS引擎第一次运行时(执行上下文-创建阶段),该函数(在=号右侧)不会被执行或存储在内存中。JS引擎将变量"greet"赋值为"undefined"。
    - 在执行期间(执行上下文-执行阶段),函数对象会即时创建(但尚未执行),并分配给'greet'变量,可以使用'greet('somename')'来调用它。

    3.立即调用函数表达式:

    例子:

    // IIFE
    var greeting = function(name) {
        return 'Namaste' + ' ' + name;
    }('Santosh')
    
    console.log(greeting)  // Namaste Santosh. 
    

    IIFE的工作原理
    - 注意在函数声明后立即使用'()'。每个函数对象都有一个附加的'CODE'属性,可以调用它(或者调用它)使用 '()'括号。
    - 在执行期间(执行上下文 - 执行阶段)创建函数对象并同时执行 - 因此,现在greeting变量的值是其返回值(字符串),而不是函数对象本身。

    JavaScript中IIFE的典型用例:

    以下IIFE模式非常常见。

    // IIFE 
    // Spelling of Function was not correct , result into error
    (function (name) {
       var greeting = 'Namaste';
       console.log(greeting + ' ' + name);
    })('Santosh');
    
    • 我们正在进行两件事情。 a) 我们将函数表达式包裹在大括号 () 中。这告诉语法解析器 () 内部的内容是一个表达式(在本例中为函数表达式),是有效的代码。
      b) 我们同时使用 () 调用此函数。

    因此,此函数将同时创建和执行 (IIFE)。

    IIFE 的重要用途:

    IIFE 使我们的代码更安全。
    - IIFE 作为一个函数,拥有自己的执行上下文,意味着在其内部创建的所有变量都是本地变量,并且不与全局执行上下文共享。

    假设我有另一个 JS 文件(test1.js)与 iife.js(请参见以下内容)一起在我的应用程序中使用。

    // test1.js
    
    var greeting = 'Hello';
    
    // iife.js
    // Spelling of Function was not correct , result into error
    (function (name) { 
       var greeting = 'Namaste';
       console.log(greeting + ' ' + name);
    })('Santosh');
    
    console.log(greeting)   // No collision happens here. It prints 'Hello'.
    

    立即调用函数表达式(IIFE)可以帮助我们编写安全的代码,避免意外地与全局对象发生冲突。


    如果我们在IIFE内创建函数,如何在其他js或jsx文件中访问它们,即在React组件中。 - stone rock
    即使我们没有使用IIFE,问候变量也不会与全局问候变量发生冲突。那么这里的优势是什么? - Willy David Jr

    15

    不,这个结构只是创建一个命名作用域。如果你将其分解成几个部分,你会发现你有一个外部的

    (...)();
    

    那是一个函数调用。括号内是:

    function() {}
    

    这是一个匿名函数。构造函数内使用var声明的所有内容只能在同一构造函数内部可见,不会污染全局命名空间。


    8

    这是一个自执行匿名函数

    查看W3Schools关于自执行函数的解释

    函数表达式可以被设置为"自执行"。

    自执行表达式会自动调用(启动),而不需要被调用。

    如果函数表达式后面跟着(),那么函数表达式会自动执行。

    你不能自执行一个函数声明。


    5
    (被称为)自执行的命名函数:(function named(){console.log("Hello");}()); - bryc
    @bryc 为什么你会给一个不需要名称的函数取名字呢? - napstercake
    4
    递归,我猜测。 - bryc

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