本文译自Ben Cherry的《JavaScript Module Pattern: In-Depth》。虽然个人不太认同js中私有变量存在的必要性,然则本文异常周全地引见了Javascript中模块化形式地各个方面。我读完今后照样受益不浅,所以翻译出来愿望对列位也有些协助。
模块化编程是一种异常罕见Javascript编程形式。它平常来讲可以使得代码更易于邃晓,然则有很多优异的实践还没有广为人知。在这篇文章中,我将会回忆一下js模块化编程的基础,而且将会讲到一些真的异常值得一提的进阶话题,包含一个我认为是我自创的形式。
基础
我们起首简朴地概述一下,自从三年前Eric Miraglia(YUI的开发者)第一次宣布博客形貌模块化形式以来的一些模块化形式。假如你已关于这些模块化形式异常熟习了,大可以直接跳过本节,从“进阶形式”最早浏览。
匿名闭包
这是一种让统统变成可以的基础构造,同时它也是Javascript最棒的特征。我们将简朴地建立一个匿名函数并马上实行它。一切的代码将跑在这个函数内,生存在一个供应私有化的闭包中,它足以使得这些闭包中的变量可以贯串我们的运用的全部生命周期。
(function () {
// ... all vars and functions are in this scope only
// still maintains access to all globals
}());
注重这对包裹匿名函数的最外层括号。由于Javascript的言语特征,这对括号是必须的。在js中由关键词function开首的语句老是会被认为是函数声明式。把这段代码包裹在括号中就可以让诠释器晓得这是个函数表达式。
全局变量导入
Javascript有一个特征叫做隐式全局变量。不管一个变量名在哪儿被用到了,诠释器会依据作用域链来反向找到这个变量的var
声明语句。假如没有找到var
声明语句,那末这个变量就会被视为全局变量。假如这个变量用在一句赋值语句中,同时这个变量又不存在时,就会建立出一个全局变量。这意味着在匿名闭包中运用或建立全局变量是很轻易的。不幸的是,这会致使写出的代码极难保护,由于关于人的直观感觉来讲,一眼基础分不清那些是全局的变量。
荣幸的是,我们的匿名函数供应了简朴的变通要领。只需将全局变量作为参数通报到我们的匿名函数中,就可以获得比隐式全局变量更清楚又疾速的代码了。下面是示例:
(function ($, YAHOO) {
// now have access to globals jQuery (as $) and YAHOO in this code
}(jQuery, YAHOO));
模块导出
偶然你不仅想要运用全局变量,你还想要声明它们,以供重复运用。我们可以很轻易地经由历程导出它们来做到这一点——经由历程匿名函数的返回值。如许做将会完成一个基础的模块化形式雏形,接下来会是一个完全的例子:
var MODULE = (function () {
var my = {},
privateVariable = 1;
function privateMethod() {
// ...
}
my.moduleProperty = 1;
my.moduleMethod = function () {
// ...
};
return my;
}());
注重我们已声清楚明了一个叫做MODULE
的全局模块,它具有2个公有的属性:一个叫做MODULE.moduleMethod
的要领和一个叫做MODULE.moduleProperty
的变量。别的,它还保护了一个应用匿名函数闭包的、私有的内置状况。同时,我们可以很轻易地导入须要的全局变量,并像之前我们所学到的那样来运用这个模块化形式。
进阶形式
上面一节所形貌的基础已足以应对很多状况,现在我们可以将这个模块化形式进一步的生长,建立更多壮大的、可扩大的构造。让我们从MODULE
模块最早,逐一引见这些进阶形式。
放大形式
全部模块必须在一个文件中是模块化形式的一个限定。任何一个介入大型项目的人都邑邃晓将js拆分多个文件的价值。荣幸的是,我们具有一个很棒的完成来放大模块。起首,我们导入一个模块,并为它增加属性,末了再导出它。下面是一个例子——从底本的MODULE
中放大它:
var MODULE = (function (my) {
my.anotherMethod = function () {
// added method...
};
return my;
}(MODULE));
我们用var关键词来保证一致性,虽然它在此处不是必须的。在这段代码实行完今后,我们的模块就已具有了一个新的、叫做MODULE.anotherMethod
的公有要领。这个放大文件也会保护它自己的私有内置状况和导入的对象。
宽放大形式
我们的上面例子须要我们的初始化模块最早被实行,然后放大模块才实行,固然偶然这可以也不一定是必须的。Javascript运用可以做到的、用来提拔机能的、最棒的事之一就是异步实行剧本。我们可以建立天真的多部份模块并经由历程宽放大形式使它们可以以恣意递次加载。每个文件都须要按下面的构造构造:
var MODULE = (function (my) {
// add capabilities...
return my;
}(MODULE || {}));
在这个形式中,var
表达式使必须的。注重假如MODULE还未初始化过,这句导入语句会建立MODULE
。这意味着你可以用一个像LABjs的东西来并行加载你一切的模块文件,而不会被壅塞。
紧放大形式
宽放大形式异常不错,但它也会给你的模块带来一些限定。最主要的是,你不能安全地掩盖模块的属性。你也没法在初始化的时刻,运用其他文件中的属性(但你可以在运转的时刻用)。紧放大形式包含了一个加载的递次序列,而且许可掩盖属性。这儿是一个简朴的例子(放大我们的原始MODULE
):
var MODULE = (function (my) {
var old_moduleMethod = my.moduleMethod;
my.moduleMethod = function () {
// method override, has access to old through old_moduleMethod...
};
return my;
}(MODULE));
我们在上面的例子中掩盖了MODULE.moduleMethod
的完成,但在须要的时刻,可以保护一个对本来要领的援用。
克隆与继续
var MODULE_TWO = (function (old) {
var my = {},
key;
for (key in old) {
if (old.hasOwnProperty(key)) {
my[key] = old[key];
}
}
var super_moduleMethod = old.moduleMethod;
my.moduleMethod = function () {
// override method on the clone, access to super through super_moduleMethod
};
return my;
}(MODULE));
这个形式多是最缺少天真性的一种挑选了。它确切使得代码显得很整齐,但那是用天真性的价值换来的。正如我上面写的这段代码,假如某个属性是对象或许函数,它将不会被复制,而是会成为这个对象或函数的第二个援用。修正了个中的某一个就会同时修正另一个(译者注:由于它们基础就是一个啊!)。这可以经由历程递归克隆历程来处理这个对象克隆题目,但函数克隆可以没法处理,或许用eval可以处理吧。因而,我在这篇文章中报告这个要领仅仅是斟酌到文章的完全性。
跨文件私有变量
把一个模块分到多个文件中有一个严重的限定:每个文件都保护了各自的私有变量,而且没法接见到其他文件的私有变量。但这个题目是可以处理的。这里有一个保护跨文件私有变量的、宽放大模块的例子:
var MODULE = (function (my) {
var _private = my._private = my._private || {},
_seal = my._seal = my._seal || function () {
delete my._private;
delete my._seal;
delete my._unseal;
},
_unseal = my._unseal = my._unseal || function () {
my._private = _private;
my._seal = _seal;
my._unseal = _unseal;
};
// permanent access to _private, _seal, and _unseal
return my;
}(MODULE || {}));
一切文件可以在它们各自的_private
变量上设置属性,而且它邃晓可以被其他文件接见。一旦这个模块加载完成,运用程序可以挪用MODULE._seal()
来防备外部对内部_private
的挪用。假如这个模块须要被从新放大,在任何一个文件中的内部要领可以在加载新的文件前挪用_unseal()
,并在新文件实行好今后再次挪用_seal()
。我现在在工作中运用这类形式,而且我在其他处所还没有见过这类要领。我以为这是一种异常有效的形式,很值得就这个形式本身写一篇文章。
子模块
我们的末了一种进阶形式是不言而喻最简朴的。建立子模块有很多优异的实例。这就像是建立平常的模块一样:
MODULE.sub = (function () {
var my = {};
// ...
return my;
}());
虽然这看上去很简朴,但我以为照样值得在这里提一提。子模块具有统统平常模块的进阶上风,包含了放大形式和私有化状况。
结论
大多数进阶形式可以连系到一起来建立一个更加有效的形式。假如实在要我引荐一种设想庞杂运用程序的模块化形式的化,我会挑选连系宽放大形式、私有变量和子模块。
我还未斟酌过这些形式的机能题目,但我情愿把这转化为一个更简朴的思索体式格局:假如一个模块化形式有很好的机能,那末它可以把最小化做的很棒,使得下载这个剧本文件更快。运用宽放大形式可以许可简朴的非壅塞并行下载,这就会加快下载速率。初始化时候可以会稍慢于其他要领,但权衡利弊后这照样值得的。只需全局变量导入正确,运转时机能应当会不会受到影响,而且另有可以在子模块中经由历程用私有变量收缩援用链来获得更快的运转速率。
作为完毕,这里是一个子模块动态地把本身加载到它的父模块的例子(假如父模块不存在则建立它)。为了简约,我把私有变量给去除了,固然加上私有变量也是很简朴的啦。这类编程形式许可一全部庞杂层级构造代码库经由历程子模块并行地完成加载。
var UTIL = (function (parent, $) {
var my = parent.ajax = parent.ajax || {};
my.get = function (url, params, callback) {
// ok, so I'm cheating a bit :)
return $.getJSON(url, params, callback);
};
// etc...
return parent;
}(UTIL || {}, jQuery));
我愿望这篇文章对你有协助,请在文章下面留言分享你的主意。从现在起,就最早写更棒、更模块化的Javascript吧!