深刻理解JavaScript 模块模式

英文:http://www.adequatelygood.com/2010/3/JavaScript-Module-Pattern-In-Depth php

模块模式是JavaScript一种经常使用的编码模式。这是通常的理解,但也有一些高级应用没有获得不少关注。在本文中,我将回顾基础知识,浏览一些不错的高级技巧,甚至我认为是原生基础的。

基础知识

首先咱们开始简单概述模型模式。三年前Eric Miraglia(YUI)的博文使模型模式众所周知。若是你已经很熟悉模型模式,能够直接阅读“高级模式”。

匿名闭包

这是一切成为可能的基础,也是JavaScript最好的特性。咱们将简单的建立匿名函数,并当即执行。全部函数内部代码都在闭包(closure)内。它提供了整个应用生命周期的私有和状态。

html

  1. (function () {html5

  2.         // ... all vars and functions are in this scope onlyajax

  3.         // still maintains access to all globals设计模式

  4. }());安全

复制代码闭包


注意匿名函数周围的()。这是语言的要求。关键字function通常认为是函数声明,包括()就是函数表达式。

引入全局

JavaScript有个特性,称为隐性全局。使用变量名称时,解释器会从做用域向后寻找变量声明。若是没找到,变量会被假定入全局(之后能够全局调用)。若是会被分配使用,在还不存在时全局建立它。这意味着在匿名函数里使用全局变量很简单。不幸的是,这会致使代码难以管理,文件中不容易区分(对人而言)哪一个变量是全局的。

幸亏,匿名函数还有一个不错的选择。全局变量做为参数传递给匿名函数。将它们引入咱们的代码中,既更清晰,又比使用隐性全局更快。下面是一个例子:

异步

  1. (function ($, YAHOO) {ide

  2.         // 当前域有权限访问全局jQuery($)和YAHOO模块化

  3. }(jQuery, YAHOO));

复制代码


模块出口

有时你不仅想用全局变量,但你须要先声明他们(模块的全局调用)。咱们用匿名函数的返回值,很容易输出他们。这样作就完成了基本的模块模式。如下是一个完整例子:

  1. var MODULE = (function () {

  2.         var my = {},

  3.                 privateVariable = 1;

  4.         

  5.         function privateMethod() {

  6.                 // ...

  7.         }

  8.         

  9.         my.moduleProperty = 1;

  10.         my.moduleMethod = function () {

  11.                 // ...

  12.         };

  13.         

  14.         return my;

  15. }());

复制代码


注意,咱们声明了一个全局模块MODULE,有两个公开属性:方法MODULE.moduleMethod和属性MODULE.moduleProperty。并且,匿名函数的闭包还维持了私有内部状态。同时学会之上的内容,咱们就很容易引入须要的全局变量,和输出到全局变量。

高级模式

对许多用户而言以上的还不足,咱们能够采用如下的模式创造强大的,可扩展的结构。让咱们使用MODULE模块,一个一个继续。

扩充

模块模式的一个限制是整个模块必须在一个文件里。任何人都了解长代码分割到不一样文件的必要。还好,咱们有很好的办法扩充模块。(在扩充文件)首先咱们引入模块(从全局),给他添加属性,再输出他。下面是一个例子扩充模块:

  1. var MODULE = (function (my) {

  2.         my.anotherMethod = function () {

  3.                 // 此前的MODULE返回my对象做为全局输出,所以这个匿名函数的参数MODULE就是上面MODULE匿名函数里的my

  4.         };


  5.         return my;

  6. }(MODULE));

复制代码


咱们再次使用var关键字以保持一致性,虽然其实不必。代码执行后,模块得到一个新公开方法MODULE.anotherMethod。扩充文件没有影响模块的私有内部状态。

松耦合扩充

上面的例子须要咱们首先建立模块,而后扩充它,这并不老是必要的。提高JavaScript应用性能最好的操做就是异步加载脚本。于是咱们能够建立灵活多部分的模块,能够将他们无顺序加载,以松耦合扩充。每一个文件应有以下的结构:

  1. var MODULE = (function (my) {

  2.         // add capabilities...

  3.         

  4.         return my;

  5. }(MODULE || {}));

复制代码


这个模式里,var语句是必须的,以标记引入时不存在会建立。这意味着你能够像LABjs同样同时加载全部模块文件而不被阻塞。

紧耦合扩充

虽然松耦合很不错,但模块上也有些限制。最重要的,你不能安全的覆写模块属性(由于没有加载顺序)。初始化时也没法使用其余文件定义的模块属性(但你能够在初始化后运行)。紧耦合扩充意味着一组加载顺序,可是容许覆写。下面是一个例子(扩充最初定义的MODULE):

  1. var MODULE = (function (my) {

  2.         var old_moduleMethod = my.moduleMethod;

  3.         

  4.         my.moduleMethod = function () {

  5.                 // method override, has access to old through old_moduleMethod...

  6.         };

  7.         

  8.         return my;

  9. }(MODULE));

复制代码


咱们覆写的MODULE.moduleMethod,但依旧保持着私有内部状态。

克隆和继承

  1. var MODULE_TWO = (function (old) {

  2.         var my = {},

  3.                 key;

  4.         

  5.         for (key in old) {

  6.                 if (old.hasOwnProperty(key)) {

  7.                         my[key] = old[key];

  8.                 }

  9.         }

  10.         

  11.         var super_moduleMethod = old.moduleMethod;

  12.         my.moduleMethod = function () {

  13.                 // override method on the clone, access to super through super_moduleMethod

  14.         };

  15.         

  16.         return my;

  17. }(MODULE));

复制代码


这种方式也许最不灵活。他能够实现巧妙的组合,可是牺牲了灵活性。正如我写的,对象的属性或方法不是拷贝,而是一个对象的两个引用。修改一个会影响其余。这可能能够保持递归克隆对象的属性固定,但没法固定方法,除了带eval的方法。不过,我已经完整的包含了模块。(其实就是作了一次浅拷贝)。

跨文件私有状态

一个模块分割成几个文件有一个严重缺陷。每一个文件都有自身的私有状态,且无权访问别的文件的私有状态。这能够修复的。下面是一个松耦合扩充的例子,不一样扩充文件之间保持了私有状态:

  1. var MODULE = (function (my) {

  2.         var _private = my._private = my._private || {},

  3.                 _seal = my._seal = my._seal || function () {

  4.                         delete my._private;

  5.                         delete my._seal;

  6.                         delete my._unseal;

  7.                 },//模块加载后,调用以移除对_private的访问权限

  8.                 _unseal = my._unseal = my._unseal || function () {

  9.                         my._private = _private;

  10.                         my._seal = _seal;

  11.                         my._unseal = _unseal;

  12.                 };//模块加载前,开启对_private的访问,以实现扩充部分对私有内容的操做

  13.         

  14.         // permanent access to _private, _seal, and _unseal

  15.         

  16.         return my;

  17. }(MODULE || {}));

复制代码


何文件均可以在本地的变量_private中设置属性,他会对别的扩充当即生效(即初始化时全部扩充的私有状态都保存在_private变量,并被my._private输出)。模块彻底加载了,应用调用MODULE._seal()方法阻止对私有属性的读取(干掉my._private输出)。若是此后模块又须要扩充,带有一个私有方法。加载扩充文件前调用MODULE._unseal()方法(恢复my._private,外部恢复操做权限)。加载后调用再seal()。

这个模式一直随我工做至今,我还没看到别的地方这样作的。我以为这个模式颇有用,值得写上。

子模块

最后的高级模式实际上最简单。有不少好方法建立子模块。和建立父模块是同样的:

  1. MODULE.sub = (function () {

  2.         var my = {};

  3.         // 就是多一级命名空间

  4.         

  5.         return my;

  6. }());

复制代码


虽然很简单,但我仍是提一下。子模块有全部正常模块的功能,包括扩充和私有状态。

总结

大多数高级模式能够互相组合成更多有用的模式。若是要我提出一个复杂应用的设计模式,我会组合松耦合、私有状态和子模块。

这里我尚未涉及性能,不过我有个小建议:模块模式是性能增益的。他简化了许多,加快代码下载。松耦合能够无阻塞并行下载,等价于提升下载速度。可能初始化比别的方法慢一点,但值得权衡。只要全局正确的引入,运行性能不会有任何损失,可能还由于局部变量和更少的引用,加快子模块的加载。

最后,一个例子动态加载子模块到父模块(动态建立)中。这里就不用私有状态了,其实加上也很简单。这段代码容许整个复杂分红的代码核心及其子模块等平行加载彻底。

  1. var UTIL = (function (parent, $) {

  2.         var my = parent.ajax = parent.ajax || {};

  3.         

  4.         my.get = function (url, params, callback) {

  5.                 // ok, so I'm cheating a bit :)

  6.                 return $.getJSON(url, params, callback);

  7.         };

  8.         

  9.         // etc...

  10.         

  11.         return parent;

  12. }(UTIL || {}, jQuery));

复制代码


我但愿你能受益的,并请发表评论,分享您的想法。 如今,继续前进,并写出更好的,更模块化的JavaScript!

相关文章
相关标签/搜索