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


584

最近,我已经阅读了很多Javascript,并且注意到整个文件都被包装成如下格式,要导入的.js文件中。

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

为什么这样做而不是使用一组简单的构造函数呢?


6
由于我想这会被很多人使用,所以请不要忘记结束语;
dgh

5
我认为该技术称为“ IIFE”。这代表立即调用函数表达式en.wikipedia.org/wiki/Immediately-invoked_function_expression
Adrien Be

Answers:


786

通常是到名称空间(请参阅下文)并控制成员函数和/或变量的可见性。可以将其视为对象定义。它的技术名称是立即调用函数表达式(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之后可以工作。


14
非常酷,我很了解名称空间,但是我已经看到了您的最后一个例子,并且无法弄清楚人们想要实现什么。这确实清除了一切。
安德鲁·寇

34
很棒的帖子。非常感谢。
达伦(Darren)

4
我认为添加前导和尾随分号';' 可以使示例更完整- ;(function(jQ) { ... code ... })(jQuery);这样,如果有人在脚本中遗漏了分号,则不会破坏您的脚本,尤其是如果您打算缩小脚本并将其与其他脚本连接时。
Taras Alenin

3
不错的帖子,我喜欢强调私有变量。我也喜欢在模块模式/闭包(public_function1&public_function2)上的开头以及如何传递变量,即使稍微超出范围还是一个不错的介绍。我还添加了一个答案,这个答案主要集中在我认为是语法的根源以及函数声明与函数表达式之间的差异以及我认为“仅是约定”与“达到此结果的唯一方法”之间的区别。
Adrien Be

4
很棒的帖子,我认为也许更多关于将变量传递到自执行函数中是如何有益的。自我执行功能中的上下文很干净-没有数据。您可以通过执行此操作来传递上下文(function (context) { ..... })(this),然后允许您将所需的任何内容附加到父上下文,从而将其公开。
卡勒姆·利宁顿

79

简而言之

摘要

以最简单的形式,该技术旨在将代码包装在函数范围内

它有助于减少以下情况的机会:

  • 与其他应用程序/库冲突
  • 污染优越(全球最有可能)的范围

,当文档准备好检测-这是不是某种document.onload也不window.onload

通常称为Immediately Invoked Function Expression (IIFE)Self Executing Anonymous Function

代码说明

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”的方法,因为我们想要的只是立即执行它。”

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

  • expression:评估价值的东西。即3+11/x
  • statement:代码行正在执行某项操作,但不会求值。即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 =

答案是“您可以”,实际上您可以通过多种方式执行此操作:在一对括号中添加a +,a !,a -或换行符(如现在所约定的那样),我相信还有更多。例如:

 (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

因此,一旦将相关修改添加到曾经的“替代代码”中,我们将返回与“代码解释”示例中使用的代码完全相同的代码

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

进一步了解Expressions vs Statements


揭秘范围

一个人可能会想知道的一件事是:“当您在函数内部未正确定义变量时,会发生什么—即执行简单赋值呢?”

(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并没有block scope(更新:在添加区块范围的局部变量ES6)。
  • javascript仅具有function scopeglobal scopewindow浏览器环境中的范围)

进一步了解Javascript Scopes


资源资源


下一步

一旦掌握了这个IIFE概念,它就会引出module pattern,这通常是通过利用这种IIFE模式来完成的。玩得开心 :)


非常有帮助。非常感谢!
Christoffer Helgelin

尼斯,我更喜欢演示版本:)
Fabrizio Bertoglio

很好的解释。谢谢!
维克拉姆·克姆拉尼

26

浏览器中的JavaScript实际上仅具有两个有效范围:函数范围和全局范围。

如果变量不在函数范围内,则在全局范围内。而且全局变量通常是不好的,因此这是一种将库的变量保留给自己的构造。


1
但是构造函数本身是否不为其变量提供作用域?
安德鲁·寇

1
是的,该库中定义的每个函数都可以定义自己的局部变量,但这允许变量在函数之间共享,而不会泄漏到库外
Gareth

@Gareth,因此允许在范围内使用“全局”变量(;
Francisco Presencia 2015年

2
@FranciscoPresencia“范围内的全局”不是一个有用的短语,因为这基本上就是“作用域”的含义。“全局”范围的全部要点是,它是所有其他范围均可访问的范围。
加雷斯2015年

19

这就是所谓的关闭。它基本上将代码密封在函数内部,以便其他库不会干扰它。这类似于以编译语言创建名称空间。

例。假设我写:

(function() {

    var x = 2;

    // do stuff with x

})();

现在,其他库无法访问x我创建的要在我的库中使用的变量。


7
小心您的术语。命名空间意味着可以通过寻址命名空间(通常使用前缀)从外部访问变量。尽管在Javascript中这是可能的,但此处未演示
Gareth 2010年

我同意它并不完全像一个名称空间,但是,您可以通过返回一个具有您要宣传的属性的对象来提供类似的功能:(function(){ ... return { publicProp1: 'blah' }; })();。显然,它与命名空间并不是完全平行的,但是以这种方式思考可能会有所帮助。
乔尔2010年

在您的示例中,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吉瓦,

!! 将值转换为其布尔(真/假)表示形式。
利亚姆

7

除了将变量保持在局部之外,一种非常方便的用法是在使用全局变量编写库时,可以给它一个较短的变量名,以在库中使用。它经常用于编写jQuery插件,因为jQuery允许您使用jQuery.noConflict()禁用指向jQuery的$变量。如果禁用它,您的代码仍然可以使用$并且只要您这样做就不会中断:

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

3
  1. 为了避免与同一窗口中的其他方法/库发生冲突,
  2. 避免使用全局范围,而将其设置为本地范围,
  3. 为了加快调试速度(本地范围),
  4. JavaScript仅具有功能范围,因此也将有助于代码的编译。

1

我们还应该在范围函数中使用“ use strict”,以确保代码应在“ strict模式”下执行。示例代码如下所示

(function() {
    'use strict';

    //Your code from here
})();

为什么要使用严格?
nbro


并不能真正回答问题!
Pritam Banerjee

Pritam,这是一种良好的做法。请在拒绝任何答案之前进行适当的研究
Neha Jain

1
“严格使用”可免除不良程序员的烦恼。而且由于大多数程序员都是糟糕的程序员,这有助于防止他们去做他们本不应该做的事情,并最终陷入迅速陷入混乱的代码中。
MattE
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.