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

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

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

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

122680 次浏览

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

的例子。假设我这样写:

(function() {


var x = 2;


// do stuff with x


})();

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

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

如果一个变量不在函数作用域中,它就是在全局作用域中。全局变量通常是不好的,所以这是一个构造,以保持库的变量本身。

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

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

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

你也可以在更大的表达式中使用像数据这样的函数闭包,就像在这个方法中确定浏览器对某些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;
})()
}

简而言之

总结

在最简单的形式中,该技术旨在将代码包装在功能范围中。

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

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

检测文档何时准备好-它不是某种document.onloadwindow.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所说)。换句话说,这些变量在函数外部是不可见/不可达的。# EYZ1。

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

# EYZ0。


迭代1

有一天,有人可能会想“必须有一种方法来避免命名‘myMainFunction’,因为我们只想立即执行它。”

如果你回到最基本的问题,你会发现:

  • expression:求值的东西。即# EYZ1
  • statement:行代码做一些事情,但它执行的值。即# EYZ1

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

 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

# EYZ0。

迭代2

下一步的想法是“如果我们根本不用var myMainFunction =,为什么要有它!?”

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

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

# EYZ0。

它不会工作,因为函数声明不可调用

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

# EYZ0。

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

当在浏览器环境中(相对于像nodejs这样的服务器环境),全局作用域由window对象定义。因此我们可以执行window.myOtherFunction()

关于这个主题,我的“良好实践”技巧是在定义任何东西时总是使用var:无论它是一个数字、对象还是函数,&即使是在全球范围内。这使得代码更加简单。

注意:

  • javascript没有block scope(更新:块作用域局部变量添加到ES6。)
  • javascript只有function scope &global scope(浏览器环境中的window范围)

阅读更多关于Javascript Scopes:


资源


下一个步骤

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

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

(function() {
'use strict';


//Your code from here
})();
  1. 为了避免与同一窗口中的其他方法/库冲突,
  2. 避免全球范围,让它成为局部范围,
  3. 为了加快调试(本地范围),
  4. JavaScript只有函数作用域,所以它也有助于代码的编译。

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

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


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

代码演示了我们可以:

  1. 在作用域中使用全局变量
  2. 导出函数、变量等。通过绑定this,这是浏览器的window对象。

它在OOP中被称为封装