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

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

为什么要这样做而不是简单的构造函数集呢?


当前回答

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

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

其他回答

它通常用于命名空间(参见稍后)和控制成员函数和/或变量的可见性。把它想象成一个对象定义。它的专业名称是立即调用函数表达式(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(),但不能调用localFunction1或localFunction2。

当你写(function(){…})(),是您正在使第一组括号内的代码成为一个函数字面量(意味着整个“对象”实际上是一个函数)。在此之后,您将自调用刚才定义的函数(final())。正如我之前提到的,这样做的主要好处是,你可以拥有私有方法/函数和属性:

(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()!这和类定义很相似。为了更好地理解这一点,我推荐以下链接进行进一步阅读:

对Javascript进行命名空间 Javascript中的私有成员(by Douglas Crockford)

EDIT

我忘了说了。在final()中,你可以在里面传递任何你想要的东西。例如,当你创建jQuery插件时,你像这样传入jQuery或$:

(function(jQ) { ... code ... })(jQuery) 

因此,您在这里要做的是定义一个接受一个参数的函数(称为jQ,一个局部变量,只有该函数知道)。然后自调用函数并传入一个参数(也称为jQuery,但这个参数来自外部,是对实际jQuery本身的引用)。这样做没有迫切的需要,但有一些好处:

您可以重新定义全局参数,并为其指定一个在局部范围内有意义的名称。 这有一点性能优势,因为在局部作用域中查找内容要快一些,而不必沿着作用域链进入全局作用域。 压缩(缩小)有好处。

Earlier I described how these functions run automatically at startup, but if they run automatically who is passing in the arguments? This technique assumes that all the parameters you need are already defined as global variables. So if jQuery wasn't already defined as a global variable this example would not work. As you might guess, one things jquery.js does during its initialization is define a 'jQuery' global variable, as well as its more famous '$' global variable, which allows this code to work after jQuery has been included.

提供一个接受的答案的例子,从https://requirejs.org/docs/whyamd.html:

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

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

代码演示了我们可以:

在作用域中使用全局变量 导出函数、变量等。通过绑定this,也就是浏览器的窗口对象。

简而言之

总结

在最简单的形式中,这种技术旨在将代码包装在函数作用域中。

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

与其他应用程序/库冲突 污染范围大(很可能是全球范围)

它不检测文档何时准备好—它不是某种文档。Onload或window.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: The Good Parts”)。


更多的细节

选择代码

最后,之前发布的代码也可以这样做:

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 语句:行代码做一些事情,但它没有求值。即如果(){}

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

 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.

or

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

or

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

感叹号在函数前有什么作用? JavaScript函数名前的加号

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

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

阅读更多关于表达式和语句的内容:

developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Expressions_and_Operators developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions # Function_constructor_vs._function_declaration_vs._function_expression Javascript:语句和表达式之间的区别? 表达式与语句


揭秘范围

人们可能会想知道的一件事是“如果你没有在函数中正确地定义变量——即做一个简单的赋值,会发生什么?”

(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只有函数作用域和全局作用域(浏览器环境中的窗口作用域)

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

var关键字的目的是什么,什么时候使用它(或省略它)? JavaScript中变量的作用域是什么?


资源

youtu.be / i_qE1iAmjFg吗?t=2m15s -保罗·爱尔兰在2:15分展示IIFE,一定要看! developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions 书:Javascript,好的部分-强烈推荐 youtu.be / i_qE1iAmjFg吗?t=4m36s - Paul Irish在4:36给出了模块模式


下一个步骤

一旦您获得了这个IIFE概念,就会得到模块模式,这通常是通过利用这个IIFE模式来完成的。玩得开心!

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

的例子。假设我这样写:

(function() {

    var x = 2;

    // do stuff with x

})();

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

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

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