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

新手上路,请多包涵

我最近阅读了很多 Javascript,我注意到整个文件在要导入的 .js 文件中像下面这样包装。

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

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

原文由 Andrew Kou 发布,翻译遵循 CC BY-SA 4.0 许可协议

阅读 454
2 个回答

它通常用于命名空间(见下文)并控制成员函数和/或变量的可见性。把它想象成一个对象定义。它的技术名称是 立即调用函数表达式(IIFE)。 jQuery 插件通常是这样写的。

在 Javascript 中,您可以嵌套函数。所以,以下是合法的:

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

Now you can call outerFunction() , but the visiblity of innerFunction() is limited to the scope of outerFunction() , meaning it is private to 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 后工作。

原文由 Vivin Paliath 发布,翻译遵循 CC BY-SA 4.0 许可协议

简而言之

概括

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

它有助于减少以下机会:

  • 与其他应用程序/库冲突
  • 污染高级(全局最有可能)范围

不会 检测文档何时准备就绪 - 它不是某种 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!'); }();

查看现场演示

它不会工作,因为 “function declarations are not invokable”

诀窍是通过删除 var myMainFunction = 我们将 函数表达式 转换为 函数声明。有关这方面的更多详细信息,请参阅“参考资料”中的链接。

下一个问题是“为什么我不能将它作为函数表达式保留为 var myMainFunction = 以外的东西?

答案是“你可以”,实际上有很多方法可以做到这一点:添加一个 + ,一个 ! ,一个 - ,或者也许 wrap在一对括号中(按照现在的惯例),我相信更多。例如:

  (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 scope & global scope ( window 浏览器环境中的作用域)

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


资源


下一步

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

原文由 Adrien Be 发布,翻译遵循 CC BY-SA 3.0 许可协议

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题