关于作用域:将整个javascript文件包装成匿名函数(如”(function()…)()”)的目的是什么?

What is the purpose of wrapping whole Javascript files in anonymous functions like “(function(){ … })()”?

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

1
2
3
4
5
(function() {
    ...
    code
    ...
})();

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


它通常用于命名(见下文)和控制成员函数和/或变量的可见性。把它想象成一个对象定义。jquery插件通常是这样编写的。

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

1
2
3
4
5
function outerFunction() {
   function innerFunction() {
      // code
   }
}

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

1
2
3
4
5
var globalVariable;

function someFunction() {
   var localVariable;
}

相应地:

1
2
3
4
5
6
7
8
9
10
function globalFunction() {

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

   function localFunction2() {
      //I'm named!
   }
}

在上述情况下,您可以从任何地方调用globalFunction(),但不能调用localFunction1localFunction2

当您编写(function() { ... code ... })()时,您所要做的是在函数文本中生成代码(意味着整个"对象"实际上是一个函数)。在这之后,您将自调用函数(最终的())。因此,正如我前面提到的,这一点的主要优势在于,您可以拥有私有的方法/函数和属性:

1
2
3
4
5
6
7
(function() {
   var private_var;

   function private_function() {
     //code
   }
})()

在第一个示例中,globalfunction()是可以调用以访问公共功能的公共函数,但在上面的示例中,如何调用它?在这里,自调用函数使代码在启动时自动运行。正如您可以将initmystuff();添加到任何.js文件的顶部,它将作为全局范围的一部分自动运行一样,这个自调用函数也将自动运行,尽管它是一个未命名的函数,不能像initmystuff()那样多次调用它。

简洁的一点是,您还可以定义内部的内容并将其公开给外部世界,因此(名称间距的一个示例,这样您基本上可以创建自己的库/插件):

1
2
3
4
5
6
7
8
9
10
11
12
13
var myPlugin = (function() {
 var private_var;

 function private_function() {
 }

 return {
    public_function1: function() {
    },
    public_function2: function() {
    }
 }
})()

现在您可以调用myPlugin.public_function1(),但不能访问private_function()!非常类似于类定义。为了更好地理解这一点,我建议您阅读以下链接:

  • 名称间距您的javascript
  • javascript中的私有成员(Douglas Crockford)

编辑

我忘了提。在最后一个()中,你可以通过任何你想进去的东西。例如,当您创建jquery插件时,您按如下方式传入jQuery$

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

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

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

前面我描述了这些函数如何在启动时自动运行,但是如果它们自动运行,那么谁来传递参数呢?此技术假定所有参数都定义为全局变量。因此,如果jquery没有定义为全局变量,那么这个示例将不起作用,并且不能以任何其他方式调用,因为我们的示例是一个匿名函数。正如您可能猜测的那样,jquery.js在初始化期间所做的一件事是定义一个"jquery"全局变量,以及更著名的"$"全局变量,它允许在包含jquery.js之后使用此代码。


简而言之总结

这种技术的最简单形式是将代码包装在函数范围内。好的。

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

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

它不检测文档准备就绪的时间——它不是某种类型的document.onloadwindow.onload。好的。

它通常被称为Immediately Invoked Function Expression (IIFE)Self Executing Anonymous Function。好的。代码说明

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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具有函数范围。"函数中定义的参数和变量在函数外部不可见,函数中定义的变量在函数内部的任何位置都可见。好的。更多细节替代代码

最后,以前发布的代码也可以按如下方式执行:好的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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(){}

同样,函数表达式的计算结果也是一个值。还有一个后果(我想?)可以立即调用它们:好的。

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

所以我们更复杂的例子是:好的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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 =!?"好的。

答案很简单:请尝试删除此项,如下所示:好的。

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

看实况演示。好的。

因为"函数声明是不可调用的"。好的。

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

下一个问题是"为什么我不能把它作为一个函数表达式保存在除var myMainFunction =之外的其他东西上?"好的。

答案是"你可以",实际上有很多方法可以做到这一点:添加一个+,一个!,一个-,或者用一对括号括起来(就像现在的惯例那样),我相信更多。例如:好的。

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

或好的。

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

或好的。

1
 -function(){ console.log('mamamia!'); }(); // live demo: jsbin.com/wejupaheva/1/edit?js,console
  • 感叹号在函数之前做什么?
  • 函数名前的javascript加号

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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的信息:好的。

  • developer.mozilla.org/en-us/docs/web/javascript/guide/expressions_和_操作符
  • developer.mozilla.org/en-us/docs/web/javascript/reference/functions function构造器vs.函数声明vs.函数表达式
  • javascript:语句和表达式之间的区别?
  • 表达式与语句

解除定义范围

有一件事我们可能会想,"当你没有在函数内部正确地定义变量时会发生什么——也就是说,做一个简单的赋值?"好的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
(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 scope(浏览器环境中的window范围)

了解更多有关Javascript Scopes的信息:好的。

  • var关键字的用途是什么?何时使用它(或忽略它)?
  • javascript中的变量范围是什么?

资源

  • 你是我吗?T=2分15秒-保罗·爱尔兰在2点15分呈现生命,一定要看这个!
  • developer.mozilla.org/en-us/docs/web/javascript/reference/functions
  • 书:javascript,好的部分-强烈推荐
  • 你是我吗?T=4m36s-Paul Irish在4:36呈现模块模式

下一步

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


浏览器中的javascript实际上只有两个有效的作用域:函数作用域和全局作用域。

如果变量不在函数范围内,则它在全局范围内。全局变量通常是不好的,所以这是一个将库变量保持为自身的构造。


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

例子。假设我写:

1
2
3
4
5
6
7
(function() {

    var x = 2;

    // do stuff with x

})();

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


您也可以在更大的表达式中使用函数闭包作为数据,也可以在这种方法中确定对某些HTML5对象的浏览器支持。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
   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;
     })()
    }


除了保持变量的局部性,一个非常方便的用法是在使用全局变量编写库时,您可以给它一个较短的变量名,以便在库中使用。它通常用于编写jquery插件,因为jquery允许您使用jquery.noconflict()禁用指向jquery的$variable。如果它被禁用,您的代码仍然可以使用$而不是中断,如果您只是这样做:

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


  • 为了避免与同一窗口中的其他方法/库发生冲突,
  • 避免全局范围,使其成为本地范围,
  • 为了加快调试速度(本地范围),
  • javascript只有函数作用域,所以它也有助于代码的编译。

  • 我们还应该在作用域函数中使用"use strict",以确保代码在"strict模式"下执行。下面显示的示例代码

    1
    2
    3
    4
    5
    (function() {
        'use strict';

        //Your code from here
    })();