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个回答

6
这是自调用匿名函数,定义后立即执行。这意味着此函数定义并在定义后立即调用。
语法解释:第一个小括号内的函数没有名称,并且在下一个小括号中调用;你可以理解为它在定义时被调用。第二个小括号内可以传递任何参数,这些参数将在第一个小括号内的函数中被获取。请看以下示例:
(function(obj){
    // Do something with this obj
})(object);

在这里,您传递的“object”将在函数内通过“obj”访问,在函数签名中抓取它。

2
这个问题已经有一个被接受的答案,你的答案没有添加任何已经被接受的答案没有涉及到的内容。因此,完全没有必要写出这个答案。 - Aadit M Shah
4
我喜欢阅读多个答案,有时一个的表述方式会有所不同。 - user755921
我认为这是添加的原因,因为它让我知道第二组括号是用来干什么的。至少在这里我看到更清楚了。 - johnny
我最喜欢的答案是:样本IIFE的两端都有参数,并且它们之间的映射是明确的。 - Stephen W. Wright

6

从这里开始:

var b = 'bee';
console.log(b);  // global

将其放入函数中,它就不再是全局的了--这是您的主要目标。

function a() {
  var b = 'bee';
  console.log(b);
}
a();
console.log(b);  // ReferenceError: b is not defined -- *as desired*

立即调用函数——糟糕:

function a() {
  var b = 'bee';
  console.log(b);
}();             // SyntaxError: Expected () to start arrow function, but got ';' instead of '=>'

使用括号以避免语法错误:
(function a() {
  var b = 'bee';
  console.log(b);
})(); // OK now

您可以省略函数名称:
(function () {    // no name required
  var b = 'bee';
  console.log(b);
})();

它不需要比那更加复杂。


2
语法错误是关于箭头函数的。据我所知,这是js的一个新功能,在几年前并不存在,但IIFE确实存在。因此,括号可能最初被用来避免语法错误,但是不同的错误? - JCarlosR
你能回答@JCarlos的问题吗?他非常正确地指出IIFE比箭头函数早得多,这将有助于理解为什么需要包装。 - Script47
@Script47 我没有JCarlos在评论中的问题的答案。你可以制定一个新问题并发布它,我相信你会得到一些好的答案。 - Jim Flood
@JCarlos 当我执行那个抛出错误的函数时,实际上我得到的是 Uncaught SyntaxError: Unexpected token ) 而不是任何关于箭头函数的提醒。你能否分享一个在箭头函数出错的情况下的 fiddle? - Script47

5

这是一个函数表达式,它代表立即调用函数表达式(IIFE)。IIFE只是一个在创建后立即执行的函数。因此,不需要等到调用该函数时才执行,而是立即执行IIFE。让我们通过示例构造IIFE。假设我们有一个add函数,它接受两个整数作为参数并返回它们的和,

步骤1:定义函数

function add (a, b){
    return a+b;
}
add(5,5);

步骤2:将整个函数声明包装在括号中,然后调用该函数。
(function add (a, b){
    return a+b;
})
//add(5,5);

第三步:要立即调用该函数,只需从调用中删除“ add”文本。
(function add (a, b){
    return a+b;
})(5,5);

使用IFFE的主要原因是为了在函数内部保留一个私有作用域。在您的JavaScript代码中,您希望确保不会覆盖任何全局变量。有时候您可能会无意中定义一个变量来覆盖一个全局变量。例如,让我们假设我们有一个名为iffe.html的HTML文件,在标签中定义如下代码:

<body>
    <div id = 'demo'></div>
    <script>
        document.getElementById("demo").innerHTML = "Hello JavaScript!";
    </script> 
</body>

好的,上面的代码将不会出现任何问题地执行,现在假设你无意或有意声明了一个名为document的变量。

<body>
    <div id = 'demo'></div>
    <script>
        document.getElementById("demo").innerHTML = "Hello JavaScript!";
        const document = "hi there";
        console.log(document);
    </script> 
</body>

如果你尝试重新声明不可配置的全局属性document,你将会遇到SyntaxError语法错误。

但是,如果你希望声明一个名为documet的变量,可以使用IFFE方法实现。

<body>
    <div id = 'demo'></div>
    <script>
        (function(){
            const document = "hi there";
            this.document.getElementById("demo").innerHTML = "Hello JavaScript!";
            console.log(document);
        })();
        document.getElementById("demo").innerHTML = "Hello JavaScript!";
    </script> 
</body>

输出:

在这里输入图片描述

让我们通过另一个例子来尝试,假设我们有一个如下的计算器对象-

<body>
    <script>
        var calculator = {
            add:function(a,b){
                return a+b;
            },
            mul:function(a,b){
                return a*b;
            }
        }
        console.log(calculator.add(5,10));
    </script> 
</body>

现在它运行得很好,但是如果我们意外地重新分配计算器对象的值呢?

<body>
    <script>
        var calculator = {
            add:function(a,b){
                return a+b;
            },
            mul:function(a,b){
                return a*b;
            }
        }
        console.log(calculator.add(5,10));
        calculator = "scientific calculator";
        console.log(calculator.mul(5,5));
    </script> 
</body>

如果不使用IFFE,你将会遇到一个TypeError: calculator.mul 不是一个函数的错误。

但是通过使用IFFE,我们可以创建一个私有作用域,在其中创建另一个名为calculator的变量并使用它。

<body>
    <script>
        var calculator = {
            add:function(a,b){
                return a+b;
            },
            mul:function(a,b){
                return a*b;
            }
        }
        var cal = (function(){
            var calculator = {
                sub:function(a,b){
                    return a-b;
                },
                div:function(a,b){
                    return a/b;
                }
            }
            console.log(this.calculator.mul(5,10));
            console.log(calculator.sub(10,5));
            return calculator;
        })();
        console.log(calculator.add(5,10));
        console.log(cal.div(10,5));
    </script> 
</body>

输出结果: 输入图像描述


4

TL;DR:表达式可以用括号括起来,如果将表达式和function的块形式结合起来,就会与函数调用冲突。

我喜欢反例,因为它们能很好地展现逻辑,并且其他人都没有列出任何反例。您可能会问:"为什么浏览器不能看到 function(){}() 并假设它是一个表达式?" 让我们通过三个例子来对比一下这个问题。

var x;

// Here, fibonacci is a block function
function fibonacci(x) {
    var value = x < 2 ? x : fibonacci(x-1) + fibonacci(x-2);
    if (x === 9) console.log("The " + x + "th fibonacci is: " + value);
    return value;
}

(x = 9);

console.log("Value of x: " + x);
console.log("fibonacci is a(n) " + typeof fibonacci);

当我们将函数转化为表达式时,观察事物如何发生变化。

var x;

// Here, fibonacci is a function expression
(function fibonacci(x) {
    var value = x < 2 ? x : fibonacci(x-1) + fibonacci(x-2);
    if (x === 9) console.log("The " + x + "th fibonacci is: " + value);
    return value;
})

(x = 9);

console.log("Value of x: " + x);
console.log("fibonacci is a(n) " + typeof fibonacci);

当您使用非运算符而不是括号时,会发生相同的事情,因为这两个运算符都将语句转换为表达式:

var x;

// Here, fibonacci is a function expression
! function fibonacci(x) {
    var value = x < 2 ? x : fibonacci(x-1) + fibonacci(x-2);
    if (x === 9) console.log("The " + x + "th fibonacci is: " + value);
    return value;
}

(x = 9);

console.log("Value of x: " + x);
console.log("fibonacci is a(n) " + typeof fibonacci);

将该函数转换为表达式后,它会被距离其下面两行的(x=9)所执行。由于表达式函数和块函数具有不同的行为方式,因此两个示例都可以在规范上运行而不会产生歧义。

名称作用域

另一个重要的观察结果是,命名的块函数在整个作用域中可见,而函数表达式仅对自己可见。换句话说,在第一个示例中,当 fibonacci 是块时,它仅对最后一个 console.log 可见。在这三个示例中,fibonacci 对自己可见,从而允许 fibonacci 调用自身,即递归。

箭头函数

逻辑的另一个方面是箭头函数。如果将块函数和表达式函数的定义合并在一起,规范就必须包含针对箭头函数的任意规则和异常情况。

function hello() {console.log("Hello World")}
(x) => console.log("hello " + x)
console.log("If you are reading this, no errors occurred");

虽然函数块可以正常工作,但跟随箭头函数的函数表达式会产生语法错误:

! function hello() {console.log("Hello World")}
(x) => console.log("hello " + x)
console.log("If you are reading this, no errors occurred");

这里存在歧义,第二行的 (x) 是调用前一行函数还是箭头函数的参数。

需要注意的是,随着时间的推移,箭头函数确实被纳入了ECMAScript标准,并且不是语言初始设计的因素;我想表达的是,区分表达式函数和块函数有助于使JavaScript的语法更加合乎逻辑和连贯。


4

自执行函数通常用于封装上下文并避免名称冲突。在 (function(){..})() 内部定义的任何变量都不是全局变量。

代码:

var same_name = 1;

var myVar = (function() {
    var same_name = 2;
    console.log(same_name);
})();

console.log(same_name);

生成以下输出:

2
1

通过使用这种语法,你可以避免与在你的JavaScript代码中声明的全局变量发生冲突。

1
正确,输出将会是2和1,因为myVar将首先被运行。 - Dalibor
1
你的解释在解释函数作用域方面做得很好,但在解释为什么它会立即执行方面不足。将其分配给变量是自我打败的,也可能意味着它可以被执行多次。var same_name = 1; var myVar = function() { var same_name = 2; console.log(same_name); }; myVar(); console.log(same_name);将会有相同的结果。 - domenicr

2

自执行匿名函数。它在创建时立即执行。

一个简短的例子是:

function prepareList(el){
  var list = (function(){
    var l = []; 
    for(var i = 0; i < 9; i++){
     l.push(i);
    }
    return l;
  })();

  return function (el){
    for(var i = 0, l = list.length; i < l; i++){
      if(list[i] == el) return list[i];
    }
    return null;
  }; 
} 

var search = prepareList();
search(2);
search(3);

因此,不必每次创建列表,只需创建一次(减少开销)。

1
你的搜索功能在每次调用时都会重建列表。为了避免这种情况,你需要(1)创建列表并且(2)将搜索函数作为闭包返回,以便访问你刚刚创建的列表。你可以使用匿名自执行形式轻松实现这一点。请参见http://jsfiddle.net/BV4bT/。 - George
能否解释一下,“少开销”这部分,我不太明白。 - HIRA THAKUR
2
开销指的是不必要的工作。在每个函数调用中填充数组是不必要的,这就是为什么在示例中数组通过自执行匿名函数只被填充一次。然而,看起来我在自己的答案中犯了一个错误,请参见George评论中的链接获取正确的示例。 - usoban

2

这被称为IIFE——立即调用函数表达式。以下是一个示例,展示了它的语法和用法。它用于将变量的使用范围限制在函数内部,而不是超出函数。

最初的回答:

它被称为IIFE-立即调用函数表达式。以下是一个示例,展示了它的语法和用法。它用于仅将变量的使用范围限制在函数内部,而不是超出函数。

(function () {
  function Question(q,a,c) {
    this.q = q;
    this.a = a;
    this.c = c;
  }

  Question.prototype.displayQuestion = function() {
    console.log(this.q);
    for (var i = 0; i < this.a.length; i++) {
      console.log(i+": "+this.a[i]);
    }
  }

  Question.prototype.checkAnswer = function(ans) {
    if (ans===this.c) {
      console.log("correct");
    } else {
      console.log("incorrect");
    }
  }

  var q1 = new Question('Is Javascript the coolest?', ['yes', 'no'], 0);
  var q2 = new Question('Is python better than Javascript?', ['yes', 'no', 'both are same'], 2);
  var q3 = new Question('Is Javascript the worst?', ['yes', 'no'], 1);

  var questions = [q1, q2, q3];

  var n = Math.floor(Math.random() * questions.length)

  var answer = parseInt(prompt(questions[n].displayQuestion()));
  questions[n].checkAnswer(answer);
})();

2

这里已经有很多好的答案了,但是我也想提供我的两分钱 :p


你可以使用IIFE(立即调用函数表达式)来:

  1. 避免全局命名空间污染。

    在 IIFE 中定义的变量(或者任何普通函数)不会覆盖全局范围内的定义。

  2. 保护代码不被外部代码访问。

    在 IIFE 中定义的所有内容只能在 IIFE 内部访问。它可以防止代码被外部代码修改。只有你明确返回作为函数结果或设置为外部变量的值才能被外部代码访问。

  3. 避免为不需要重复使用的函数命名。

    尽管在 IIFE 模式中可以使用命名函数,但通常不这样做,因为没有必要重复调用它。

  4. 用于通用模块定义,这在许多 JS 库中都被使用。详见此问题


IIFE 通常以以下方式使用:

(function(param){
   //code here
})(args);

您可以省略匿名函数周围的括号(),并在匿名函数之前使用void运算符。
void function(param){
   //code here
}(args);

1
以下代码:
(function () {

})();

被称为立即执行函数表达式(IIFE)。

它被称为函数表达式,因为Javascript中的( yourcode )运算符将其强制转换为表达式。 函数表达式函数声明之间的区别如下:

// declaration:
function declaredFunction () {}

// expressions:

// storing function into variable
const expressedFunction = function () {}

// Using () operator, which transforms the function into an expression
(function () {})

表达式是一堆可以求值为单个值的代码。在上面的示例中,这些表达式的值是一个单独的函数对象。
当我们有一个能够求值为函数对象的表达式后,我们便可以立即使用()运算符调用该函数对象。例如:

(function() {

  const foo = 10;        // all variables inside here are scoped to the function block
  console.log(foo);

})();

console.log(foo);  // referenceError foo is scoped to the IIFE

为什么这很有用?

当我们处理大型代码库和/或导入各种库时,命名冲突的机会增加。当我们编写与之相关的某些代码部分(因此使用相同的变量)放在IIFE内部时,所有变量和函数名称都作用于IIFE的函数括号中。这减少了命名冲突的可能性,并使您可以更轻松地对它们进行命名(例如,您不必添加前缀)。


1

另一个使用案例是记忆化,其中缓存对象不是全局的:

var calculate = (function() {
  var cache = {};
  return function(a) {

    if (cache[a]) {
      return cache[a];
    } else {
      // Calculate heavy operation
      cache[a] = heavyOperation(a);
      return cache[a];
    }
  }
})();

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