用“(function(){…})()”等匿名函数包装整个Javascript文件的目的是什么?
最近我一直在阅读大量的Javascript,并且我一直注意到整个文件在导入的.js文件中被包装成如下所示。
(function() { ... code ... })();
这样做的原因是什么,而不是一个简单的构造函数的设置?
通常是命名空间(见后面),并控制成员函数和/或variables的可见性。 把它想象成一个对象定义。 jQuery插件通常是这样写的。
在Javascript中,你可以嵌套函数。 所以,以下是合法的:
function outerFunction() { function innerFunction() { // code } }
现在可以调用outerFunction()
,但innerFunction()
的可见性仅限于outerFunction()
的范围,这意味着它对于outerFunction()
是私有的。 它基本上遵循与Javascript中的variables相同的原则:
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() { ... code ... })()
,你正在做什么,你是在一个函数字面量内(这意味着整个“对象”实际上是一个函数)的代码。 之后,你自己调用函数(final ()
)。 所以我之前提到的主要优点是可以拥有私有的方法/函数和属性:
(function() { var private_var; function private_function() { //code } })()
在第一个例子中,globalFunction()是可以被调用来访问公共function的公共函数,但是在上面的例子中,你怎么称呼它呢? 这里的自调用函数使代码在启动时自动运行。 就像你可以添加initMyStuff(); 到任何.js文件的顶部,它将作为全局作用域的一部分自动运行,这个自调用函数也会自动运行,尽pipe它是一个未命名的函数,不能像initMyStuff()那样多次调用它。
整洁的事情是,你也可以定义内部的东西,并将其暴露给外部世界(命名空间的一个例子,所以你可以基本上创build自己的库/插件):
var myPlugin = (function() { var private_var; function private_function() { } return { public_function1: function() { }, public_function2: function() { } } })()
现在你可以调用myPlugin.public_function1()
,但是你不能访问private_function()
! 非常类似于类定义。 为了更好地理解这一点,我build议以下链接进一步阅读:
- 命名空间的Javascript
- Javascript中的私人成员(Douglas Crockford)
编辑
我忘了提及。 在最后的()
,你可以传递任何你想要的东西。 例如,当您创buildjQuery插件时,您传递jQuery
或$
像这样:
(function(jQ) { ... code ... })(jQuery)
所以你在这里做的是定义一个函数,它接受一个参数(称为jQ
,一个局部variables, 只有该函数)。 然后你自我调用函数并传入一个参数(也称为jQuery
,但是这个来自外部世界,并且是对实际jQuery本身的引用)。 没有迫切需要这样做,但有一些优点:
- 你可以重新定义一个全局参数并给它一个在本地范围内有意义的名字。
- 在性能方面略有优势,因为在本地范围内查看速度要快一些,而不必将范围链向全球范围走。
- 有压缩(缩小)的好处。
之前我描述过这些函数是如何在启动时自动运行的,但是如果它们自动运行谁传递参数呢? 这种技术假设所有的参数都被定义为全局variables。 所以,如果jQuery没有被定义为全局variables,那么这个例子将不起作用,因为我们的例子是一个匿名函数,所以不能被调用。 正如你可能猜到的,jquery.js在初始化过程中所做的一件事情是定义一个“jQuery”全局variables,以及更为着名的“$”全局variables,它允许在包含jquery.js之后工作。
简而言之
概要
以最简单的forms,这种技术旨在将代码包装在函数范围内 。
它有助于减less的机会:
- 与其他应用程序/库冲突
- 污染优越(全球最有可能)的范围
它不检测文档何时准备好 – 它不是某种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
在上面的例子中,函数中定义的任何variables(即使用var
声明)都将是“私有的”,并且只能在函数范围内(如Vivin Paliath所说)访问。 换句话说,这些variables在函数之外是不可见/可访问的。 看现场演示 。
JavaScript有function范围。 “函数中定义的参数和variables在函数之外是不可见的,函数内任何地方定义的variables在函数内部都是可见的。” (来自“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(){}
类似地,函数expression式评估为一个值。 其中一个结果(我假设?)是可以立即调用的:
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 =
我们将函数expression式转换为函数声明 。 请参阅“资源”中的链接以获取更多详细信息。
接下来的问题是“为什么我不能把它作为一个函数expression式而不是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
- 感叹号在函数之前做了什么?
- 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
阅读更多关于Expressions vs Statements
:
- 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:语句和expression之间的区别?
- expression与声明
揭秘范围
有一件事可能会问,“当你不在函数内部正确地定义variables时会发生什么 – 也就是做一个简单的赋值?
(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
看现场演示 。
基本上,如果一个未在当前范围中声明的variables被分配了一个值,那么“查找范围链直到它findvariables或命中全局范围(在这个点上它将创build它)”。
在浏览器环境中(与像nodejs这样的服务器环境相比),全局作用域是由window
对象定义的。 因此我们可以做window.myOtherFunction()
。
我在这个主题上的“良好实践”小贴士是在定义任何东西时总是使用var
:是数字,对象还是函数,甚至是在全局范围内。 这使得代码更简单。
注意:
- JavaScript没有
block scope
(更新:在ES6中添加块范围局部variables。) - javascript只有
function scope
和global scope
(浏览器环境中的window
范围)
阅读更多关于Javascript Scopes
:
- var关键字的用途是什么以及何时使用(或省略)?
- JavaScript中variables的范围是什么?
资源
- 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
概念,它就会导致module pattern
,这通常是通过利用这个IIFE模式来完成的。 玩的开心 :)
在浏览器中的Javascript只有一个有效的范围:函数范围和全局范围。
如果一个variables不在函数范围内,则它在全局范围内。 而全局variables通常是不好的,所以这是一个构造来保持图书馆的variables本身。
这就是所谓的封闭。 它基本上封闭了函数内的代码,以便其他库不会干扰它。 这与在编译语言中创build名称空间类似。
例。 假设我写:
(function() { var x = 2; // do stuff with x })();
现在其他库不能访问我在库中创build的variablesx
。
您也可以使用函数闭包作为更大expression式中的数据 ,就像在这个确定某些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; })() }
除了将variables保存在本地之外,使用全局variables编写库的一个非常方便的用法是,可以给它一个较短的variables名称以在库中使用。 它通常用于编写jQuery插件,因为jQuery允许您使用jQuery.noConflict()来禁用指向jQuery的$variables。 如果它被禁用,你的代码仍然可以使用$而不会中断,如果你只是这样做:
(function($) { ...code...})(jQuery);
- 为避免与同一窗口中的其他方法/库冲突,
- 避免全球范围,使其成为本地范围,
- 为了加快debugging速度(本地范围),
- JavaScript只有函数范围,所以它也会帮助编译代码。
我们还应该在范围函数中使用“严格使用”来确保代码应该在“严格模式”下执行。 示例代码如下所示
(function() { 'use strict'; //Your code from here })();