Module模式是JavaScript编程中一个很是通用的模式,通常状况下,你们都知道基本用法,本文尝试着给你们更多该模式的高级使用方式。编程
首先咱们来看看Module模式的基本特征:闭包
先看一下最简单的一个实现,代码以下:模块化
var Calculator = function (eq) { //这里能够声明私有成员 var eqCtl = document.getElementById(eq); return { // 暴露公开的成员 add: function (x, y) { var val = x + y; eqCtl.innerHTML = val; } }; };
咱们能够经过以下的方式来调用:函数
var calculator = new Calculator('eq'); calculator.add(2, 2);
你们可能看到了,每次用的时候都要new一下,也就是说每一个实例在内存里都是一份copy,若是你不须要传参数或者没有一些特殊苛刻的要求的话,咱们能够在最后一个}后面加上一个括号,来达到自执行的目的,这样该实例在内存中只会存在一份copy,不过在展现他的优势以前,咱们仍是先来看看这个模式的基本使用方法吧。性能
匿名闭包是让一切成为可能的基础,而这也是JavaScript最好的特性,咱们来建立一个最简单的闭包函数,函数内部的代码一直存在于闭包内,在整个运行周期内,该闭包都保证了内部的代码处于私有状态。spa
(function () { // ... 全部的变量和function都在这里声明,而且做用域也只能在这个匿名闭包里 // ...可是这里的代码依然能够访问外部全局的对象 }());
注意,匿名函数后面的括号,这是JavaScript语言所要求的,由于若是你不声明的话,JavaScript解释器默认是声明一个function函数,有括号,就是建立一个函数表达式,也就是自执行,用的时候不用和上面那样在new了,固然你也能够这样来声明:设计
function () {/* 内部代码 */})();
不过咱们推荐使用第一种方式,关于函数自执行,我后面会有专门一篇文章进行详解,这里就很少说了。code
JavaScript有一个特性叫作隐式全局变量,无论一个变量有没有用过,JavaScript解释器反向遍历做用域链来查找整个变量的var声明,若是没有找到var,解释器则假定该变量是全局变量,若是该变量用于了赋值操做的话,以前若是不存在的话,解释器则会自动建立它,这就是说在匿名闭包里使用或建立全局变量很是容易,不过比较困难的是,代码比较难管理,尤为是阅读代码的人看着不少区分哪些变量是全局的,哪些是局部的。
不过,好在在匿名函数里咱们能够提供一个比较简单的替代方案,咱们能够将全局变量当成一个参数传入到匿名函数而后使用,相比隐式全局变量,它又清晰又快,咱们来看一个例子:对象
(function ($, YAHOO) { // 这里,咱们的代码就可使用全局的jQuery对象了,YAHOO也是同样 } (jQuery, YAHOO));
如今不少类库里都有这种使用方式,好比jQuery源码。blog
不过,有时候可能不只仅要使用全局变量,而是也想声明全局变量,如何作呢?咱们能够经过匿名函数的返回值来返回这个全局变量,这也就是一个基本的Module模式,来看一个完整的代码:
var blogModule = (function () { var my = {}, privateName = "博客园"; function privateAddTopic(data) { // 这里是内部处理代码 } my.Name = privateName; my.AddTopic = function (data) { privateAddTopic(data); }; return my; } ());
上面的代码声明了一个全局变量blogModule,而且带有2个可访问的属性:blogModule.AddTopic和blogModule.Name,除此以外,其它代码都在匿名函数的闭包里保持着私有状态。同时根据上面传入全局变量的例子,咱们也能够很方便地传入其它的全局变量。
上面的内容对大多数用户已经很足够了,但咱们还能够基于此模式延伸出更强大,易于扩展的结构,让咱们一个一个来看。
Module模式的一个限制就是全部的代码都要写在一个文件,可是在一些大型项目里,将一个功能分离成多个文件是很是重要的,由于能够多人合做易于开发。再回头看看上面的全局参数导入例子,咱们可否把blogModule自身传进去呢?答案是确定的,咱们先将blogModule传进去,添加一个函数属性,而后再返回就达到了咱们所说的目的,上代码:
var blogModule = (function (my) { my.AddPhoto = function () { //添加内部代码 }; return my; } (blogModule));
这段代码,看起来是否是有C#里扩展方法的感受?有点相似,但本质不同哦。同时尽管var不是必须的,但为了确保一致,咱们再次使用了它,代码执行之后,blogModule下的AddPhoto就可使用了,同时匿名函数内部的代码也依然保证了私密性和内部状态。
上面的代码尽管能够执行,可是必须先声明blogModule,而后再执行上面的扩展代码,也就是说步骤不能乱,怎么解决这个问题呢?咱们来回想一下,咱们平时声明变量的都是都是这样的:
var cnblogs = cnblogs || {} ;
这是确保cnblogs对象,在存在的时候直接用,不存在的时候直接赋值为{},咱们来看看如何利用这个特性来实现Module模式的任意加载顺序:
var blogModule = (function (my) { // 添加一些功能 return my; } (blogModule || {}));
经过这样的代码,每一个单独分离的文件都保证这个结构,那么咱们就能够实现任意顺序的加载,因此,这个时候的var就是必需要声明的,由于不声明,其它文件读取不到哦。
虽然松耦合扩展很牛叉了,可是可能也会存在一些限制,好比你没办法重写你的一些属性或者函数,也不能在初始化的时候就是用Module的属性。紧耦合扩展限制了加载顺序,可是提供了咱们重载的机会,看以下例子:
var blogModule = (function (my) { var oldAddPhotoMethod = my.AddPhoto; my.AddPhoto = function () { // 重载方法,依然可经过oldAddPhotoMethod调用旧的方法 }; return my; } (blogModule));
经过这种方式,咱们达到了重载的目的,固然若是你想在继续在内部使用原有的属性,你能够调用oldAddPhotoMethod来用。
var blogModule = (function (old) { var my = {}, key; for (key in old) { if (old.hasOwnProperty(key)) { my[key] = old[key]; } } var oldAddPhotoMethod = old.AddPhoto; my.AddPhoto = function () { // 克隆之后,进行了重写,固然也能够继续调用oldAddPhotoMethod }; return my; } (blogModule));
这种方式灵活是灵活,可是也须要花费灵活的代价,其实该对象的属性对象或function根本没有被复制,只是对同一个对象多了一种引用而已,因此若是老对象去改变它,那克隆之后的对象所拥有的属性或function函数也会被改变,解决这个问题,咱们就得是用递归,但递归对function函数的赋值也很差用,因此咱们在递归的时候eval相应的function。无论怎么样,我仍是把这一个方式放在这个帖子里了,你们使用的时候注意一下就好了。
经过上面的例子,咱们知道,若是一个module分割到多个文件的话,每一个文件须要保证同样的结构,也就是说每一个文件匿名函数里的私有对象都不能交叉访问,那若是咱们非要使用,那怎么办呢? 咱们先看一段代码:
var blogModule = (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; }; return my; } (blogModule || {}));
任何文件均可以对他们的局部变量_private设属性,而且设置对其余的文件也当即生效。一旦这个模块加载结束,应用会调用 blogModule._seal()"上锁",这会阻止外部接入内部的_private。若是这个模块须要再次增生,应用的生命周期内,任何文件均可以调用_unseal() ”开锁”,而后再加载新文件。加载后再次调用 _seal()”上锁”。
最后一个也是最简单的使用方式,那就是建立子模块
blogModule.CommentSubModule = (function () { var my = {}; // ... return my; } ());
尽管很是简单,我仍是把它放进来了,由于我想说明的是子模块也具备通常模块全部的高级使用方式,也就是说你能够对任意子模块再次使用上面的一些应用方法。
上面的大部分方式均可以互相组合使用的,通常来讲若是要设计系统,可能会用到松耦合扩展,私有状态和子模块这样的方式。另外,我这里没有提到性能问题,但我认为Module模式效率高,代码少,加载速度快。使用松耦合扩展容许并行加载,这更能够提高下载速度。不过初始化时间可能要慢一些,可是为了使用好的模式,这是值得的。