本文深入解析JavaScript Module模式,涵盖从基础封装到高级扩展的完整用法。内容涉及匿名闭包、全局变量引用、松耦合与紧耦合扩展、克隆继承及跨文件私有对象共享,帮助开发者构建高内聚、低耦合的可复用代码结构。
Module模式概述
概述
Module模式在JavaScript开发中应用广泛,大众熟知其基础用法,本文旨在深入探讨该模式的高级应用场景。
首先分析Module模式的核心特征:
- 具备模块化与可重用性
- 实现变量与函数的封装,避免与全局命名空间产生交互,保持松耦合状态
- 仅对外暴露公共方法,其余私有方法均被隐藏
该概念最早由YUI团队成员Eric Miraglia在四年前提出,下文将通过简单示例解析基础用法(若已熟悉可跳过此节)。
基本用法与匿名闭包
基础用法
首先展示最简实现,代码示例如下:
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操作,导致内存中生成多个副本。若无需传参或无特殊要求,可在末尾括号后添加自执行括号,使内存中仅保留一份实例。但在展示优势前,先了解基本用法。
匿名闭包
匿名闭包是实现该模式的基础,这也是JavaScript的显著特性。构建最简闭包函数,其内部代码始终处于闭包环境中,并在整个运行周期内保持私有状态。
(function () {
// ... 所有的变量和function都在这里声明,并且作用域也只能在这个匿名闭包里
// ...但是这里的代码依然可以访问外部全局的对象
}());
注意匿名函数后的括号,这是JavaScript语法要求。若不声明括号,解释器默认视为函数声明;加上括号则构成函数表达式,即自执行,无需像前例那样使用new。也可采用如下声明方式:
(function () {/* 内部代码 */})();
推荐采用第一种方式。关于函数自执行的详解将在后续文章单独讨论,此处略过。
全局变量处理
引用全局变量
JavaScript存在隐式全局变量特性。无论变量是否已使用,解释器会逆向遍历作用域链查找var声明;若未找到var,则假定其为全局变量。若该变量用于赋值且此前不存在,解释器会自动创建。这使得在匿名闭包中使用或创建全局变量变得容易,但缺点是代码管理困难,难以区分全局与局部变量。
然而,在匿名函数中提供简单替代方案:将全局变量作为参数传入匿名函数并使用。相比隐式全局变量,此法更清晰高效,示例如下:
(function ($, YAHOO) {
// 这里,我们的代码就可以使用全局的jQuery对象了,YAHOO也是一样
} (jQuery, YAHOO));
此类用法常见于诸多类库,例如jQuery源码。
有时需声明全局变量而非仅使用,可通过匿名函数返回值返回该全局变量,构成基本Module模式,完整代码示例如下:
var blogModule = (function () {
var my = {}, privateName = "博客园";
function privateAddTopic(data) {
// 这里是内部处理代码
}
my.Name = privateName;
my.AddTopic = function (data) {
privateAddTopic(data);
};
return my;
} ());
上述代码声明了全局变量blogModule,并具备两个可访问属性: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));
此方式虽灵活,但也需付出相应代价。实际上,该对象的属性或函数并未被复制,仅是对同一对象的多重引用。若原对象发生改变,克隆后的对象属性或函数亦会随之改变。为解决此问题,需使用递归,但递归对函数赋值效果不佳,故在递归时需eval相应函数。无论如何,将此方式纳入本文,使用时请注意。
跨文件共享与子模块
跨文件共享私有对象
通过前述示例可知,若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模式效率高、代码量少、加载速度快。使用松耦合扩展允许并行加载,可进一步提升下载速度。尽管初始化时间可能稍慢,但为使用优良模式,此举值得。
以上为全面解析JavaScript Module模式的详细内容,更多关于JavaScript Module模式的资料请关注本站其它相关文章!