javascript模块模式深度探索 豆瓣javascript组译文2

前言

模块模式是基于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跨文件私有状态

将一个模块划分到多个文件的限制之一是每个文件保持它自己的私有状态,而且不能解接入其他文件的私有状态。这是可以解决的,下面的例子用松散增生模块在多个增生之间保持私有状态:

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;
};

// 持久的接入 _private, _seal, 和 _unseal

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

任何文件都可以对他们的局部变量_private设属性,并且设置对其他的文件也立即生效。一旦这个模块加载结束,应用会调用MODULE._seal()"上锁",这会阻止外部接入内部的_private。如果这个模块需要再次增生,应用的生命周期内,任何文件都可以调用_unseal() ”开锁”,然后再加载新文件。加载后再次调用 _seal()”上锁”


Sub-modules子模块

我们最后的高级模式是最简单的,有很多好例子来创建子模块,就像创建一个普通的模块

MODULE.sub = (function () {
var my = {};
// ...

return my;
}());

尽管这很明显,但我认为还是值得加进来的,子模块具有一般模块所有的高级能力,包括增生和私有状态。

/*==豆瓣javascript组译文2==*/

总结

大多数高级模式可以与其他的互相组合形成有用的模式。如果让我来设计一个复杂应用的架构,我会组合使用loose augmentation, private state, 和 sub-modules.

这里我并没有研究性能问题。但是我想顺便提一句:模块模式效率很好。代码量可以更少,使加载代码更快。使用 loose augmentation允许简单的非阻碍式并行加载,这更可以提升下载的速度。初始化时间可能比其他方法要慢,但是这是值得的。Run-time performance should suffer no penalties so long as globals are imported correctly, and will probably gain speed in sub-modules by shortening the reference chain with local variables.

最后,这里有个sub-module动态的把自己加载到父模块去(如果没有则创建)。为了简洁,我没有包含private state。这段代码允许一个复杂的大型分层代码库并行的加载自己和他的子模块:

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));

未校对,有两句还有翻译,请路过的同学提供这方面的协助

-END-




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

  • Whyme.Lyu
    2010-03-24 12:47:32 Whyme.Lyu (I smell shit in the air)

    [未译成的那段]
    这个模式可能是最缺乏灵活性的. 尽管它实现了简洁的功能组合, 但代价就是牺牲灵活性. 以我的测试情况, 模块属性中的函数和对象_并未_被复制(1), 其结果就是同一个对象被多重引用. 在一处对对象的修改在另一处也可见. 有一种应对方案是以递归的形式完成深拷贝(2), 但对于函数来说还是难办(3), 可能要借助eval才能办到. 处于完整性的考虑, 我还是把这种模式收录在此.

    译注:
    1. 因为是按照引用传递而非传值
    2. jQuery.extend提供了一个典型递归深拷贝的实现~可以参考
    http://github.com/jquery/jquery/blob/master/src/core.js#L292
    3. 如果不在函数上面再添加属性就没关系.

    ---

    另外再添加一个CommonJS的模块规范:
    http://commonjs.org/specs/modules/1.0/

    每个JS文件都有自己的全局作用域, 也可以简单理解为每个js文件都被自决匿名函数包起来. 出于习惯, 还是尽量先用var声明模块内部的变量.
    每个JS文件范围内都有一个exports自由变量. 模块的公开接口要扩充在这个对象上.
    每个JS文件范围内都有一个require函数, 可以返回被require的文件中的exports对象的引用, 从而允许使用目标模块的API.

    QUnit的例子:

    qunit.js文件中:

    var QUnit = {....}
    ...
    if ( typeof exports === "undefined" || typeof require === "undefined" ) {
    extend(window, QUnit); //浏览器环境~
    window.QUnit = QUnit;
    } else {
    extend(exports, QUnit); //CommonJS环境
    exports.QUnit = QUnit;
    }

    调用该模块的环境:
    var unitTest = require('lib/qunit'); //不需要.js扩展名, 可以使用相对/绝对路径, 为其分配的局部变量名字可以任意指定.
    unitTest.ok(unitTest.ok.length === unitTest.QUnit.ok.length, 'working');

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值