日期:2014-05-16  浏览次数:20443 次

javascript模块模式深度探索
前言

模块模式是基于js闭包实现的一个模式,这篇文章描述如何用模块模式来支持多人大型项目,此外,需要自己做框架的同学也可以参考。

-煎蛋



模块模式深度探索模块模式是一个常用的js编程模式。它很好理解,但是还有一些高级的使用方法没有引起广泛的注意。这篇文章,我将回顾一些不寻常的高端话题,其中一个是我自认为原创的。

基础

我们先来简述一下模块模式。三年前YUI的Eric Miraglia首先发博客(http://yuiblog.com/blog/2007/06/12/module-pattern/)提到这个模式后,模块模式变得广为人知。如果你已经非常了解模块模式,可以跳到"高级模式“的段落。

匿名闭包

匿名闭包是让一切成为可能的基础,而且这也是javascript最好的特性。我们创建个简单的匿名函数(anonymous function)看看。函数内运行的代码都存在于闭包内,这个闭包在整个应用的生命周期内都保持私密和自己的状态(privacy and state)。

(function () {
// 所有的var和function都只存在于当前作用域内
// 仍然可以读取到所有的全局变量
}());

注意 包住匿名函数的"()"。这是JS语言本身的要求,因为由function开头的代码一律被识别为"函数声明",用()包住则创建了一个函数表达式。

*** http://www.cn-cuckoo.com/wordpress/wp-content/uploads/2009/12/named-function-expressions-demystified.html 关于函数声明VS函数表达式 ***

Global Import 引用全局变量

js有个特质”隐含的全局变量“(implied globals)。无论一个name是否使用过,JS解释器反向遍历作用域链查找这个name的var声明,如果没找到var,则这个对象是全局的。这意味着在一个匿名闭包中使用和创建全局变量很容易。不幸的是这让代码难与管理,对阅读代码的人来说很难区分哪些变量是全局的。

幸好,我们的匿名函数提供了一个简单的替代方案。将全局变量作为参数传入匿名函数,这比用隐含全局变量更清晰更快速。例子:

(function ($, YAHOO) {
// 使用全局的 jquery 比如$ 和 YAHOO
}(jQuery, YAHOO));


Module Export 模块导出

当你不仅仅想使用全局变量,还想声明一些(全局变量)的时候。我们可以很方便地用匿名函数的返回值来导出(全局变量)。 这么做就是一个完整的模块模式基本形态。例子:

var MODULE = (function () {
var my = {},
privateVariable = 1;

function privateMethod() {
// ...
}

my.moduleProperty = 1;
my.moduleMethod = function () {
// ...
};

return my;
}());


我们声明了一个全局变量”MODULE”, 有两个公有属性: 分别是一个方法MODULE.moduleMethod和一个变量MODULE.moduleProperty。除此之外,它用匿名函数的闭包保持自己的私有内部状态。同时根据上一个例子,我们还可以很方便的引用全局变量。

高级模式

上面的内容对大多数用户已经很足够了,但我们还可以基于此模式发展出更强大,易于扩展的结构。

Augmentation增生

模块模式的一个限制是整个模块必须写在一个文件里。在大型编码项目里工作的人都知道代码分成多个文件的重要性。(再次)幸好,我们又一个很好的解决方案 augment modules

首先,我们导入模块,然后我们添加属性,然后我们再把它导出。例子:

var MODULE = (function (my) {
my.anotherMethod = function () {
//添加一些方法
};

return my;
}(MODULE));


为确保一致性我们再次使用var关键字,尽管这不是必须的。代码运行后,我们的模块会获得一个新的公有方法MODULE.anotherMethod。这个增生的文件也保持自己的私密性,内部状态和对他的导入。

Loose Augmentation松散增生

我们的上一个例子要求我们的初始化模块必须先运行。而增生必须第二步发生。这不应该是必要的。
js的好处之一就是可以异步的读取脚本文件。我们可以创建灵活的多块的模块,用Loose Augmentation,他们可以按任何顺序加载自己。每个文件应该有如下的结构:

var MODULE = (function (my) {
// 添加一些功能

return my;
}(MODULE || {}));


在这个模式下,var声明总是必须的注意如果模块还不存在,导入就会新建模块。这意味着你可以使用类似LABjs(http://labjs.com/)这样的工具并行的读取所有你的模块文件,没有任何的阻塞(block)


Tight Augmentation紧密增生

虽然松散增生很牛叉,但是这对你的模块有一定的限制。最重要的是你不能安全的重载(override)你的模块属性.你也不能在初始化的时候就使用模块的属性。紧密增生包含一个加载顺序,但是允许重载(override).例子:

var MODULE = (function (my) {
var old_moduleMethod = my.moduleMethod;

my.moduleMethod = function () {
// 重载方法,可通过old_moduleMethod调用旧的方法
};

return my;
}(MODULE));


这样我们重载(override)了MODULE.moduleMethod,但如果需要,仍可保持对原方法的引用。


Cloning and Inheritance 克隆和继承
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 () {
//在克隆里重载方法,通过super_moduleMethod接入父级(super)
};

return my;
}(MODULE));


引用
这个模式可能是最灵活的选择。他允许一些neat compositions。这会带来一些灵活性上的代价。I have written it, properties which are objects or functions will not be duplicated, they will exist as one object with two references. Changing one will change the other. This could be fixed for objects with a recursive cloning process, but probably cannot be fixed for functions, except perhaps with eval. Nevertheless, I've included it for completeness.


Cross-File Private State跨文件私有状态