将整个Javascript文件包装在匿名函数“(function(){ … })()”中的目的是什么?

642

最近我一直在阅读很多Javascript,我注意到整个文件都像以下代码一样被包裹在.js文件中以进行导入。

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

为什么要这样做,而不是简单地使用构造函数集合?


9
既然我想这将被许多人使用,请不要忘记结束符号“;”。 - dgh
6
这种技术被称为“IIFE”,我认为。这代表立即调用函数表达式。http://en.wikipedia.org/wiki/Immediately-invoked_function_expression - Adriano
2
可能是JavaScript 中自执行函数的目的是什么?的重复问题。 - Bergi
10个回答

849

通常会使用命名空间(见后文)来控制成员函数和/或变量的可见性,类似于对象定义。技术上称之为立即调用函数表达式(IIFE)。jQuery 插件通常是这样编写的。

在 JavaScript 中,可以将函数嵌套。因此,以下代码是合法的:

function outerFunction() {
   function innerFunction() {
      // code
   }
}

现在您可以调用outerFunction(),但是innerFunction()的可见性仅限于outerFunction()的范围内,这意味着它对outerFunction()是私有的。它基本上遵循Javascript变量的相同原则:

var globalVariable;

function someFunction() {
   var localVariable;
}

相应地:

function globalFunction() {

   var localFunction1 = function() {
       //I'm anonymous! But localFunction1 is a reference to me!
   };

   function localFunction2() {
      //I'm named!
   }
}
在上述场景中,您可以从任何地方调用globalFunction(),但你不能调用localFunction1localFunction2
当你写(function() { ... })()时,你实际上是将第一组括号内的代码作为函数字面量(整个“对象”实际上是一个函数)。然后,你自我调用刚刚定义的函数(最后的())。因此,如我之前所提到的,这样做的主要优点是可以拥有私有方法/函数和属性。
(function() {
   var private_var;

   function private_function() {
     //code
   }
})();
在第一个例子中,你需要通过名称显式调用globalFunction来运行它。也就是说,只需要使用globalFunction()来运行它。但在上面的例子中,你不仅定义了一个函数,还一次性定义并调用了它。这意味着当你的 JavaScript 文件被加载时,它会立即执行。当然,你也可以这样做:
function globalFunction() {
    // code
}
globalFunction();
行为大体相同,但有一个重要区别:使用立即执行函数表达式(IIFE)可以避免污染全局作用域(因此这也意味着您无法多次调用该函数,因为它没有名称,但由于此函数只需要执行一次,所以这真的不是问题)。
IIFE 的好处在于,您还可以在内部定义内容,并仅向外界公开您想要展示的部分(例如命名空间,这样您基本上就可以创建自己的库/插件)。
var myPlugin = (function() {
 var private_var;

 function private_function() {
 }

 return {
    public_function1: function() {
    },
    public_function2: function() {
    }
 }
})()
现在你可以调用myPlugin.public_function1(),但你无法访问private_function()!这与类定义非常相似。为了更好地理解,我建议阅读以下链接以获取更多信息: 编辑 我忘了提到。在最后的()中,你可以传入任何想要的东西。例如,当你创建jQuery插件时,你可以像这样传递jQuery$
(function(jQ) { ... code ... })(jQuery) 
你正在定义一个函数,它接受一个参数(称为jQ,这是一个局部变量,仅在该函数中可知)。然后你自动调用该函数并传入一个参数(也称为jQuery,但这个参数来自外部世界,并引用实际的jQuery本身)。这样做没有迫切需要,但有一些优点:
  • 你可以重新定义全局参数,并给它一个在本地范围内有意义的名称。
  • 在局部范围内查找东西比在全局范围内沿着作用域链向上查找要快得多,因此有轻微的性能优势。
  • 有助于压缩(缩小)体积。
早些时候我描述了这些函数如何在启动时自动运行,但如果它们自动运行,那么谁会传递参数呢?这种技术假定你需要的所有参数都已经被定义为全局变量。因此,如果jQuery尚未定义为全局变量,则此示例将无法正常工作。正如你可能猜到的那样,jquery.js在初始化过程中执行的一件事就是定义'jQuery'全局变量以及更著名的'$'全局变量,这使得在包含jQuery之后此代码可以正常工作。

16
非常棒,我很好地理解了命名空间,但是我看到了很多你最后一个例子的代码,却无法理解人们试图实现什么。这真的澄清了事情。 - Andrew Kou
4
我认为在示例中加入前导和尾随分号 ";" 会使其更完整 - ;(function(jQ) { ... code ... })(jQuery); 这样,如果有人在他们的脚本中漏掉了一个分号,它不会破坏你的代码,特别是如果你计划将你的脚本与其他脚本进行压缩和连接。 - Taras Alenin
3
很好的帖子,我喜欢强调私有变量。我也喜欢模块模式/闭包的开头(public_function1和public_function2)以及如何传递变量,尽管略微超出范围,但是这是一个不错的介绍。我还添加了一个答案,重点介绍语法的根源和函数声明与函数表达式之间的区别,以及我认为是“只是一种约定”与“实现此结果的唯一方法”的区别。 - Adriano
4
很棒的帖子,我认为可以更多地介绍如何将变量传递到自执行函数中的好处。自执行函数的上下文是干净的 - 没有数据。你可以通过这样做来传递上下文 (function (context) { ..... })(this),从而允许你将任何你喜欢的内容附加到父级上下文中,从而使其暴露出来。 - Callum Linington
3
很棒的帖子! 我想补充一点,在这个环境中,在代码最后加上一个导出声明也很常见(有时这是这个匿名函数定义中仅有的可执行部分)。因此,大多数库都以 this.myLib = myLibdefine(myLib)module.exports = myLib 或这些类型的组合结尾。 - Roberto Stelling
显示剩余3条评论

82

简述

概述

简单来说,这种技术旨在将代码包装在一个函数范围内。

它有助于减少以下的可能性:

  • 与其他应用程序/库冲突
  • 污染更高级别(全局最可能)作用域

不能检测文档是否就绪,它不是document.onloadwindow.onload之类的东西。

它通常被称为立即调用的函数表达式(IIFE)自执行匿名函数

代码解释

var someFunction = function(){ console.log('wagwan!'); };

(function() {                   /* function scope starts here */
  console.log('start of IIFE');

  var myNumber = 4;             /* number variable declaration */
  var myFunction = function(){  /* function variable declaration */
    console.log('formidable!'); 
  };
  var myObject = {              /* object variable declaration */
    anotherNumber : 1001, 
    anotherFunc : function(){ console.log('formidable!'); }
  };
  console.log('end of IIFE');
})();                           /* function scope ends */

someFunction();            // reachable, hence works: see in the console
myFunction();              // unreachable, will throw an error, see in the console
myObject.anotherFunc();    // unreachable, will throw an error, see in the console

在上面的示例中,函数内定义的任何变量(即使用var声明的变量)都将是“私有的”,仅在函数范围内可访问(正如Vivin Paliath所说)。换句话说,这些变量在函数外部不可见/无法访问。 查看实时演示
Javascript具有函数作用域。 “在函数中定义的参数和变量在函数外部不可见,并且在函数内部定义的变量在函数的任何位置都可见。” (来自“ Javascript:好部分”)。

更多细节

替代代码

最后,之前发布的代码也可以按以下方式完成:

var someFunction = function(){ console.log('wagwan!'); };

var myMainFunction = function() {
  console.log('start of IIFE');

  var myNumber = 4;
  var myFunction = function(){ console.log('formidable!'); };
  var myObject = { 
    anotherNumber : 1001, 
    anotherFunc : function(){ console.log('formidable!'); }
  };
  console.log('end of IIFE');
};

myMainFunction();          // I CALL "myMainFunction" FUNCTION HERE
someFunction();            // reachable, hence works: see in the console
myFunction();              // unreachable, will throw an error, see in the console
myObject.anotherFunc();    // unreachable, will throw an error, see in the console

查看实时演示


起源

迭代 1

有一天,可能有人想到“一定有一种方法可以避免命名'myMainFunction',因为我们想要的只是立即执行它。”

如果你回到基础知识,你会发现:

  • 表达式:某个值的评估方式。例如:3+11/x
  • 语句:执行某些操作的代码行,但它不会评估为一个值。例如:if(){}

类似地,函数表达式会评估为一个值。并且一个后果(我猜?)是它们可以被立即调用:

 var italianSayinSomething = function(){ console.log('mamamia!'); }();

所以我们更加复杂的例子变成了:
var someFunction = function(){ console.log('wagwan!'); };

var myMainFunction = function() {
  console.log('start of IIFE');

  var myNumber = 4;
  var myFunction = function(){ console.log('formidable!'); };
  var myObject = { 
    anotherNumber : 1001, 
    anotherFunc : function(){ console.log('formidable!'); }
  };
  console.log('end of IIFE');
}();

someFunction();            // reachable, hence works: see in the console
myFunction();              // unreachable, will throw an error, see in the console
myObject.anotherFunc();    // unreachable, will throw an error, see in the console

查看实时演示.

迭代2

下一步是思考“如果我们甚至不使用var myMainFunction =,为什么要有它呢?”。

答案很简单:试着将其删除,如下所示:

 function(){ console.log('mamamia!'); }();

查看实时演示

它不起作用的原因是"函数声明无法调用"

诀窍在于通过删除var myMainFunction =,我们将函数表达式转换为函数声明。有关此内容的更多详细信息,请参见“资源”中的链接。

下一个问题是:“为什么我不能将其保留为除var myMainFunction =之外的其他函数表达式?”

答案是:“你可以”,实际上有许多方法可以做到这一点:添加+!-或者可能在一对括号中包装(按照约定现在已经完成),还有更多我相信。例如:

 (function(){ console.log('mamamia!'); })(); // live demo: jsbin.com/zokuwodoco/1/edit?js,console.

或者

 +function(){ console.log('mamamia!'); }(); // live demo: jsbin.com/wuwipiyazi/1/edit?js,console

或者

 -function(){ console.log('mamamia!'); }(); // live demo: jsbin.com/wejupaheva/1/edit?js,console

因此,一旦将相关修改添加到曾经是“备选代码”的代码中,我们回到与“Code Explained”示例中使用的完全相同的代码

var someFunction = function(){ console.log('wagwan!'); };

(function() {
  console.log('start of IIFE');

  var myNumber = 4;
  var myFunction = function(){ console.log('formidable!'); };
  var myObject = { 
    anotherNumber : 1001, 
    anotherFunc : function(){ console.log('formidable!'); }
  };
  console.log('end of IIFE');
})();

someFunction();            // reachable, hence works: see in the console
myFunction();              // unreachable, will throw an error, see in the console
myObject.anotherFunc();    // unreachable, will throw an error, see in the console

了解更多关于 表达式 vs 语句 的内容:


揭秘作用域

有一件事情你可能会好奇,那就是“如果你没有在函数内部 '正确' 定义变量,而只是进行了简单的赋值操作,会发生什么?”

(function() {
  var myNumber = 4;             /* number variable declaration */
  var myFunction = function(){  /* function variable declaration */
    console.log('formidable!'); 
  };
  var myObject = {              /* object variable declaration */
    anotherNumber : 1001, 
    anotherFunc : function(){ console.log('formidable!'); }
  };
  myOtherFunction = function(){  /* oops, an assignment instead of a declaration */
    console.log('haha. got ya!');
  };
})();
myOtherFunction();         // reachable, hence works: see in the console
window.myOtherFunction();  // works in the browser, myOtherFunction is then in the global scope
myFunction();              // unreachable, will throw an error, see in the console

查看演示.

基本上,如果在当前作用域中未声明变量,则分配值时会发生“查找作用域链,直到找到变量或命中全局范围(此时将创建变量)”。

在浏览器环境中(与像nodejs这样的服务器环境相比),全局范围由window对象定义。因此,我们可以执行window.myOtherFunction()

我对这个主题的“良好实践”提示是:无论定义什么,都要始终使用var:无论是数字、对象还是函数,甚至在全局范围内。这使得代码更简单。

注意:

  • Javascript没有块级作用域(更新:在ES6中添加了块级作用域局部变量)
  • Javascript只有函数作用域全局作用域(在浏览器环境中为window作用域)

阅读更多关于Javascript作用域的内容:


资源


下一步操作

一旦你理解了这个 IIFE 的概念,它会引导你进入 模块模式,通常是通过利用这个 IIFE 模式来完成的。祝玩得开心 :)


非常有帮助。非常感谢! - Christoffer Helgelin Hald
很好,我更喜欢演示版 :) - Fabrizio Bertoglio
这是一个非常好的解释。谢谢! - Vikram Khemlani

27

在浏览器中,JavaScript 只有两个有效的作用域:函数作用域和全局作用域。

如果变量不在函数作用域中,它就在全局作用域中。而全局变量通常是不好的,因此这是一种将库的变量保持私有的构造方式。


1
但是构造函数本身不会为其自己的变量提供作用域吗? - Andrew Kou
1
是的,此库中定义的每个函数都可以定义自己的本地变量,但这允许变量在函数之间共享,而不会泄漏到库外。 - Gareth
@Gareth,这允许在作用域内使用“全局”变量(; - Francisco Presencia
2
@FranciscoPresencia,“范围内的全局”不是一个有用的短语,因为这基本上就是“范围”的意思。 “全局”范围的整个重点在于它是所有其他范围都可以访问的特定范围。 - Gareth

20

这被称为闭包。它基本上将函数内的代码封装起来,以防止其他库干扰。它类似于在编译语言中创建命名空间。

例如,假设我写了以下代码:

(function() {

    var x = 2;

    // do stuff with x

})();

现在其他库无法访问我创建用于自己库中的变量 x


7
注意术语的使用。 命名空间意味着变量可以通过命名空间的地址(通常是使用前缀)从外部访问。虽然这在JavaScript中是可能的,但这里演示的不是这种情况。请注意术语的准确性。 - Gareth
我同意它并不完全像命名空间,但是你可以通过返回一个带有你想要公开的属性的对象来提供类似的功能:(function(){ ... return { publicProp1: 'blah' }; })();。显然这并不完全等同于命名空间,但这种思考方式可能会有所帮助。 - Joel
在你的例子中,x仍然是一个私有变量...尽管你将它包装在IIFE中。试着在函数外部访问x,你会发现无法访问。 - RayLoveless
你的观点是无效的。即使在以下函数中,其他库也无法访问x。function() { var x = 2 } - RayLoveless
@RayLoveless 我同意。我不反驳那个说法。事实上,我在这个答案的最后一句也做出了同样的断言。 - Joel

8
您可以将函数闭包作为“数据”在更大的表达式中使用,就像在确定某些html5对象的浏览器支持的方法中一样。
   navigator.html5={
     canvas: (function(){
      var dc= document.createElement('canvas');
      if(!dc.getContext) return 0;
      var c= dc.getContext('2d');
      return typeof c.fillText== 'function'? 2: 1;
     })(),
     localStorage: (function(){
      return !!window.localStorage;
     })(),
     webworkers: (function(){
      return !!window.Worker;
     })(),
     offline: (function(){
      return !!window.applicationCache;
     })()
    }

!! 做什么? - 1.21 gigawatts
1
!! 将一个值转换为它的布尔(真/假)表示形式。 - Liam

7

除了将变量保持本地化外,其中一个非常方便的用法是在使用全局变量编写库时,您可以为其提供较短的变量名称以在库内使用。它经常用于编写jQuery插件,因为jQuery允许您禁用指向jQuery的$变量,使用jQuery.noConflict()。如果被禁用,您的代码仍然可以使用$而不会中断,只需执行如下操作:

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

3
  1. 为避免与同一窗口中的其他方法/库发生冲突,
  2. 避免使用全局作用域,而是使用局部作用域,
  3. 使调试更快(使用局部作用域),
  4. JavaScript仅具有函数作用域,因此它也有助于编译代码。

1
我们还应该在作用域函数中使用"use strict",以确保代码在“严格模式”下执行。示例代码如下所示。
(function() {
    'use strict';

    //Your code from here
})();

1
为什么我们应该使用严格模式? - nbro
请查看这篇文章:https://dev59.com/ynM_5IYBdhLWcg3wiDuA - Avinash Jain
1
并没有真正回答问题! - Pritam Banerjee
Pritam,这是一个好的实践。在投票反对任何答案之前,请进行适当的研究。 - Avinash Jain
1
"use strict" 可以防止糟糕的程序员自作聪明。由于大多数程序员都是糟糕的程序员,它有助于防止他们做一些绝对不应该做的事情,并最终陷入代码混乱的境地。 - MattE

0

https://requirejs.org/docs/whyamd.html提供被接受答案的示例:

(function () {
    var $ = this.jQuery;

    this.myExample = function () {};
}());

代码示范了我们可以:
  1. 在作用域内使用全局变量
  2. 通过将函数、变量等绑定到this来导出它们,其中this是浏览器的window对象。

0

这在面向对象编程中被称为封装


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