【问题标题】:What is the purpose of wrapping whole Javascript files in anonymous functions like “(function(){ … })()”?将整个 Javascript 文件包装在像“(function(){ ... })()”这样的匿名函数中的目的是什么?
【发布时间】:2011-01-26 04:29:14
【问题描述】:

我最近一直在阅读大量的 Javascript,我注意到整个文件在要导入的 .js 文件中如下所示。

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

这样做的原因是什么,而不是一组简单的构造函数?

【问题讨论】:

标签: javascript scope coding-style iife


【解决方案1】:

通常用于命名空间(见下文)并控制成员函数和/或变量的可见性。把它想象成一个对象定义。它的技术名称是立即调用函数表达式 (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,但 this 来自外部世界,并且是对实际 jQuery 本身的引用)。没有迫切需要这样做,但有一些好处:

  • 您可以重新定义全局参数,并为其命名在本地范围内有意义。
  • 性能稍有优势,因为在本地范围内查找内容更快,而不必沿着范围链向上进入全局范围。
  • 压缩(缩小)有好处。

之前我描述了这些函数如何在启动时自动运行,但如果它们自动运行,谁在传递参数?该技术假定您需要的所有参数都已定义为全局变量。因此,如果 jQuery 尚未定义为全局变量,则此示例将不起作用。正如您可能猜到的,jquery.js 在其初始化期间所做的一件事是定义一个“jQuery”全局变量,以及它更著名的“$”全局变量,它允许此代码在包含 jQuery 后工作。

【讨论】:

  • 非常酷,我非常了解命名空间,但我已经看过很多你的最后一个示例,无法弄清楚人们想要实现的目标。这真的很清楚。
  • 很棒的帖子。非常感谢。
  • 我想添加一个前导和尾随分号';'将使示例完整 - ;(function(jQ) { ... code ... })(jQuery); 这样,如果有人在他们的脚本中遗漏了一个分号,它不会破坏你的,特别是如果你打算缩小你的脚本并将其与其他脚本连接。
  • 不错的帖子,我喜欢强调私有变量。我也喜欢模块模式/闭包(public_function1 和 public_function2)的开头以及传递变量的方式,即使稍微超出范围也是一个很好的介绍。我还添加了一个答案,这个答案侧重于我认为语法的根源以及函数语句与函数表达式之间的差异以及我认为“只是一种约定”与“实现此结果的唯一方法”。跨度>
  • 很棒的帖子,我认为可能更多关于如何将变量传递给自执行函数是有益的。自执行函数中的上下文是干净的 - 没有数据。您可以通过执行此(function (context) { ..... })(this) 来传递上下文,然后您可以将任何您喜欢的内容附加到父上下文,从而公开它。
【解决方案2】:

总之

总结

以最简单的形式,这种技术旨在将代码包装在函数范围中。

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

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

检测文档何时准备就绪 - 它不是某种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 所说)。换句话说,这些变量在函数之外是不可见/不可访问的。 See live demo.

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

See live demo.


根源

迭代 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

See live demo.

迭代 2

下一步是思考“如果我们甚至不使用var myMainFunction =!?”。

答案很简单:尝试删除它,如下所示:

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

See live demo.

它不起作用,因为“函数声明不可调用”

诀窍在于,通过删除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

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

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

See live demo.

基本上,如果未在其当前作用域中声明的变量被赋值,那么“查找作用域链直到找到变量或到达全局作用域(此时它将创建它)” .

在浏览器环境中(与 nodejs 等服务器环境相比),全局范围由 window 对象定义。因此我们可以做到window.myOtherFunction()

我关于此主题的“良好做法”提示是在定义任何内容时始终使用var:无论是数字、对象还是函数,甚至在全局范围内也是如此。这使得代码更简单。

注意:

  • javascript 没有block scope(更新:在ES6 中添加了块范围局部变量。)
  • javascript 只有function scopeglobal scope(浏览器环境中的window 范围)

阅读更多关于Javascript Scopes的信息:


资源


后续步骤

一旦你得到这个IIFE 概念,它就会导致module pattern,这通常是通过利用这个IIFE 模式来完成的。玩得开心:)

【讨论】:

  • 非常有帮助。非常感谢!
  • 很好,我更喜欢 demo 版本:)
  • 这样一个很好的解释。谢谢!
【解决方案3】:

浏览器中的Javascript实际上只有几个有效范围:函数范围和全局范围。

如果变量不在函数范围内,则它在全局范围内。而且全局变量通常很糟糕,所以这是一个将库变量保留给自身的构造。

【讨论】:

  • 但是构造函数本身不是为自己的变量提供作用域吗?
  • 是的,这个库中定义的每个函数都可以定义自己的局部变量,但这允许变量在函数之间共享而不会泄漏到库之外
  • @Gareth,因此这允许范围内的“全局”变量 (;
  • @FranciscoPresencia “范围内的全局”不是一个有用的短语,因为这基本上就是“范围”的意思。 “全局”范围的全部意义在于它是 所有 其他范围都可以访问的范围。
【解决方案4】:

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

示例。假设我写:

(function() {

    var x = 2;

    // do stuff with x

})();

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

【讨论】:

  • 小心你的术语。命名空间意味着可以通过寻址命名空间(通常使用前缀)从外部访问变量。虽然这在 Javascript 中是可能的,但这不是这里演示的内容
  • 我同意它与命名空间不完全一样,但是,您可以通过返回具有您想要公开的属性的对象来提供类似的功能:(function(){ ... return { publicProp1: 'blah' }; })();。显然与命名空间不完全平行,但这样想可能会有所帮助。
  • 在你的例子中 x 仍然是一个私有变量......尽管你将它包装在一个 IIFE 中。继续尝试在函数之外访问 x,你不能..
  • 您的观点无效。即使在以下函数中,其他库也无法访问 x。函数() { var x = 2 }
  • @RayLoveless 我同意。我不反对这种说法。事实上,我和这个答案的最后一句话做出了同样的断言。
【解决方案5】:

您也可以在较大的表达式中将函数闭包用作数据,就像在这种确定浏览器对某些 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;
     })()
    }

【讨论】:

  • 什么!!做什么?
  • !!将一个值转换为它的布尔(真/假)表示。
【解决方案6】:

除了将变量保持在本地之外,一个非常方便的用途是在使用全局变量编写库时,可以给它一个更短的变量名以在库中使用。它经常用于编写 jQuery 插件,因为 jQuery 允许您使用 jQuery.noConflict() 禁用指向 jQuery 的 $ 变量。如果它被禁用,您的代码仍然可以使用 $ 而不会中断:

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

【讨论】:

    【解决方案7】:
    1. 为避免与同一窗口中的其他方法/库发生冲突,
    2. 避免全局范围,使其成为本地范围,
    3. 为了加快调试速度(本地范围),
    4. JavaScript 仅具有函数作用域,因此它也有助于编译代码。

    【讨论】:

      【解决方案8】:

      我们还应该在作用域函数中使用“使用严格”来确保代码应该在“严格模式”下执行。示例代码如下所示

      (function() {
          'use strict';
      
          //Your code from here
      })();
      

      【讨论】:

      • 为什么要使用 strict?
      • 没有真正回答问题!
      • Pritam,它是一个很好的实践使用。请在否决任何答案之前进行适当的研究
      • 'use strict' 可以避免糟糕的程序员。而且由于大多数程序员都是糟糕的程序员,这有助于防止他们做他们绝对不应该做的事情并最终导致代码迅速下沉。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2020-08-11
      • 2012-01-08
      • 2022-11-10
      • 2019-02-08
      相关资源
      最近更新 更多