(function() {})()构造函数是如何工作的,为什么人们使用它?

80

(function() {})()以及它在jQuery中的对应形式(function($) {})(jQuery)经常出现在JavaScript代码中。

这些结构是如何工作的,以及它们解决了哪些问题?

请提供示例。


2
请参见以下链接:https://dev59.com/CnRC5IYBdhLWcg3wAcQ3 - bobince
2
这不是完全重复的问题。其他问题的答案并没有真正说明为什么要将jQuery作为参数提供。 - Georg Schölly
1
我一直想知道为什么需要额外的括号。我认为这与花括号中的块和对象字面量之间的歧义有关。请注意,如果您正在分配给某个变量,例如var x = function(){}(),则不需要这些括号也可以正常工作。 - jpsimons
1
为什么这个问题被标记为“不是一个真正的问题”? 这个问题很清楚而且相关。 - Chris
15个回答

75
随着JavaScript框架的日益普及,$符号被用于许多不同的场合。为了缓解可能的冲突,您可以使用这些构造:
(function ($){
  // Your code using $ here.
})(jQuery);

具体来说,这是一个匿名函数声明,它会立即执行并将主要的jQuery对象作为参数传递。在该函数内部,您可以使用$来引用该对象,而无需担心其他框架也在作用域中。


4
简短有意义的回答。+1! - pestaa
5
这在技术上是一个函数表达式,而不是函数声明。 在括号内立即调用该函数(IIFE)。 - John Strickler
你能解释一下你所说的“......而不必担心其他框架也在范围内。”是什么意思吗? - Redtopia
@Redtopia。Javascript的作用域是在函数级别上定义的。函数外的所有内容都在全局作用域中。想想一个网页中包含了多少个Javascript。每个函数作用域外的变量都有可能发生冲突。 - Alberto De Caro
谢谢澄清...我之前并不清楚你所指的是匿名函数的本地作用域,它无法从全局作用域或任何其他函数的作用域中看到。 - Redtopia

41

这是一种限制变量作用域的技巧;这是防止变量污染全局命名空间的唯一方法。

var bar = 1; // bar is now part of the global namespace
alert(bar);

(function () {
   var foo = 1; // foo has function scope
   alert(foo); 
   // code to be executed goes here
})();

7
仍然可以让它们在你的代码中“全局”使用。 - tvanfosson
那么示例代码中对Opera的引用是不相关/错误的吗? - Bobby Jack
1
@Bobby:在那个代码块中可能会有一些仅供Opera使用的额外变量/函数。 - geowa4

19

1)它定义了一个匿名函数,并立即执行该函数。

2)通常这样做是为了避免在全局命名空间中添加不需要的代码。

3)您需要从其中公开一些方法,任何在函数内声明的内容都将是“私有”的,例如:

MyLib = (function(){
    // other private stuff here
    return {
        init: function(){
        }
    };

})();

或者,另一种选择是:

MyLib = {};
(function({
    MyLib.foo = function(){
    }
}));

问题在于,你可以用很多方法来使用它,但结果始终如一。


  1. 这样的一个函数被定义在一个单独的文件中,它是如何执行的?(似乎没有人调用这个函数,那么这个函数是如何被触发的?)
  2. 我没有看到顶层的函数有一个 "return" 语句...
我正在阅读 dojo.util._doh._browserRunner.js谢谢!
- Paul
  1. 就像我说的那样,它会自行执行,最后的()告诉它要执行。
  2. 它不一定需要返回值,也可能在特定属性上设置值。这只是一个例子,我更新了答案,提供了另一种可能性。
- Evan Trimboli

11

这只是一个被立即调用的匿名函数。你也可以先创建这个函数,然后再调用它,得到同样的效果:

(function(){ ... })();

作用如下:

temp = function(){ ... };
temp();

你也可以使用命名函数实现同样的效果:

function temp() { ... }
temp();

你所称为jQuery特定代码只是因为它在其中使用了jQuery对象。它只是一个带参数的匿名函数,会立即调用。

你可以用两个步骤完成同样的事情,并且可以使用任何你喜欢的参数:

temp = function(answer){ ... };
temp(42);
这解决的问题是在函数中创建闭包。你可以在其中声明变量而不污染全局命名空间,从而降低使用一个脚本与另一个脚本时发生冲突的风险。对于 jQuery 的特定情况,您可以在兼容模式下使用它,该模式不会将名称 $ 声明为 jQuery 的别名。通过将 jQuery 对象发送到闭包中并将参数命名为 $,您仍然可以使用与无兼容模式相同的语法。

正如我在@spoulson的回答中所评论的那样,匿名函数并不等同于闭包。 - Tim Down

7

它在这里解释了,你的第一个构造函数提供了变量作用域。

在javascript中,变量的作用域是函数级别的。这与像C#或Java这样的语言不同,其中变量的作用域限于块。这意味着如果你在循环或if语句内声明一个变量,它将对整个函数可用。

如果你发现自己需要在函数内明确地给变量分配作用域,可以使用匿名函数来实现。你实际上可以创建一个匿名函数,然后立即执行它,所有内部的变量都将被限定在匿名函数中:

(function() {
  var myProperty = "hello world";
  alert(myProperty);
})();
alert(typeof(myProperty)); // undefined

7

另一个做这件事的原因是消除你使用哪个框架的$操作符的混淆。例如,要强制使用jQuery,可以这样做:

;(function($){
   ... your jQuery code here...
})(jQuery);

通过将$运算符作为参数传递并在jQuery上调用它,即使您加载了其他框架,该函数内部的$运算符也会被锁定到jQuery。


这是对原问题的更具体的变化,但仍然是有用的信息! :) - Bobby Jack
这并不是一个答案,但我为你所提出的绝妙想法点了赞! - Spidey

5

这种结构的另一个用途是“捕获”将在闭包中使用的局部变量的值。例如:

for (var i = 0; i < 3; i++) {
    $("#button"+i).click(function() {
        alert(i);
    });
}

上述代码会使所有三个按钮弹出“3”。另一方面:
for (var i = 0; i < 3; i++) {
    (function(i) {
        $("#button"+i).click(function() {
            alert(i);
        });
    })(i);
}

这将使得三个按钮按照预期弹出"0","1"和"2"。
原因是闭包会保留对其封闭堆栈帧的引用,该帧保存其变量的当前值。如果这些变量在闭包执行之前发生更改,则闭包将只看到最新的值,而不是它们在创建闭包时的值。通过像上面的第二个示例中那样将闭包创建包装在另一个函数内部,变量i的当前值将保存在匿名函数的堆栈帧中。

一个小问题,但不是闭包闭合的是栈帧,而是周围的词法作用域。良好的闭包实现只会捕获在闭包内部实际提到的符号,有些实现仅仅捕获所有内容(Ruby就是这样,所以要小心与闭包附近的代码)。把面向对象中的对象看做是一个显式定义了捕获符号的闭包是一种思考方式(即对象的成员属性)。 - KayEss
这是真的,但从理论上来看,我们可以认为堆栈帧是闭包引用的内容,因此这只是一个实现问题。在Lisp家族语言中,采用闭包完成所有的面向对象编程样式“对象”,这是一种强大的技术。 - Greg Hewgill
是的,尽管如果您想从堆栈帧的角度考虑,那么闭包就是一系列堆栈帧,每个封闭函数在词法作用域中都有一个帧,连同全局作用域。词法作用域更容易思考(特别是当向新手解释闭包时),因为当您有多层嵌套函数时,您不会陷入无意义(且具有误导性)的讨论中,即哪个堆栈帧。词法作用域只是源代码中看到的东西。 - KayEss

4

这被认为是一个闭包。这意味着包含的代码将在其自己的词法作用域内运行。这意味着您可以定义新变量和函数,并且它们不会与闭包外部使用的命名空间发生冲突。

var i = 0;
alert("The magic number is " + i);

(function() {
   var i = 99;
   alert("The magic number inside the closure is " + i);
})();

alert("The magic number is still " + i);

这将生成三个弹出窗口,演示闭包中的i不会改变同名的预先存在的变量:

  • 魔术数字是0
  • 闭包内部的魔术数字是99
  • 魔术数字仍然是0

5
这不是闭包,只是展示一个函数有自己的作用域。当一个函数在另一个函数内被定义并且在该函数外部可用时,就会形成闭包,因此即使外部函数已经返回,它仍然保留对外部函数中的变量、函数和参数的访问权限。 - Tim Down
只需将“(function() {...”更改为“var func = function() {...}; func();”。这样,闭包就完成了。 - spoulson
我不想再赘述了,但是...仍然没有闭包: 在外部函数之外没有创建内部函数。在你的例子中创建闭包的简单方法是返回一个弹出i的函数:var f = (function() { var i = 99; return function() { alert(i); }; })(); f(); - Tim Down
注意到了。这取决于具体的实现细节。 - spoulson

2

就像其他人所说的一样,它们都定义了立即调用的匿名函数。我通常会在JavaScript类声明中使用这个结构,以创建一个静态私有作用域,使该作用域中的常量数据、静态方法、事件处理程序或其他任何内容仅对类的实例可见。

// Declare a namespace object.
window.MyLibrary = {};

// Wrap class declaration to create a private static scope.
(function() {
  var incrementingID = 0;

  function somePrivateStaticMethod() {
    // ...
  }

  // Declare the MyObject class under the MyLibrary namespace.
  MyLibrary.MyObject = function() {
    this.id = incrementingID++;
  };

  // ...MyObject's prototype declaration goes here, etc...
  MyLibrary.MyObject.prototype = {
    memberMethod: function() {
      // Do some stuff
      // Maybe call a static private method!
      somePrivateStaticMethod();
    }
  };
})();

在这个例子中,MyObject类被分配到MyLibrary命名空间中,因此可以访问。 incrementingIDsomePrivateStaticMethod()不能在匿名函数作用域之外直接访问。

2

基本上就是给你的 JavaScript 代码打上了命名空间。

例如,你可以把任何变量或函数放在其中,而从外部来看,它们不存在于该作用域中。因此,当你将所有内容封装在其中时,就不必担心冲突问题。

结尾处的()表示自我调用。你还可以在那里添加一个参数,它将成为匿名函数的参数。我经常在 jQuery 中使用这种方式,你可以看到原因……

(function($) {

    // Now I can use $, but it won't affect any other library like Prototype
})(jQuery);

Evan Trimboli在他的回答中涵盖了其余部分。


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