- 模块化简介实现方式低级写法对象写法立刻执行函数(Immediately-Invoked Function Expression,IIFE) or 匿名闭包放大模式 or 宽放大模式(Loose augmentation)构造函数引用全局变量基本的Module模式Module扩展方式松耦合扩展紧耦合扩展跨文件共享私有对象参考资料
模块化简介
需要模块管理的原因就是JavaScript发展的越来越快,超过了它产生时候的自我定位。由于没有模块管理的概念,在做大型项目或者文件组织的时候,就会异常纠结。所以才会产生出这么多的模块管理工具。模块化的主要特征是:
- 模块化,可重用
- 封装了变量和function,和全局的namaspace不接触,松耦合
- 只暴露可用public的方法,其它私有方法全部隐藏实现方式
低级写法
初级阶段,代码中充斥的是各种函数,通过名称区分:
function func1() {}function func2() {}
实际上这些函数都被挂载到了全局变量下,使用的时候可以直接调用,缺点就是: “污染”了全局变量,无法保证不与其他模块发生变量名冲突,而且模块成员之间看不出直接关系。
对象写法
解决上述污染问题,可以把模块写成一个对象,所有的模块成员都放到这个对象里面。
var module1 = new Object({ _count : 0, m1 : function (){ //... }, m2 : function (){ //... } });// 等价于var module2 = { _count: 0, m1 : function (){ //... }, m2 : function (){ //... }}
外部可以通过定义的变量名访问,起到了一点点的命名空间作用,但是同样缺点明显: 会暴露所有模块成员,内部状态可以被外部改写。比如,外部代码可以直接改变内_count的值。
立刻执行函数(Immediately-Invoked Function Expression,IIFE) or 匿名闭包
如果你不需要传参数或者没有一些特殊苛刻的要求的,可以使用立刻执行函数,该实例在内存中只会存在一份copy。
var module1 = (function(){ var _count = 0; var m1 = function(){ //... }; var m2 = function(){ //... }; return { m1 : m1, m2 : m2 }; })();
这样可以很好的保护私有变量,通过return来设置公开的方法。缺点也有: 动态添加方法的时候比较麻烦,且无法修改内部私有变量.
放大模式 or 宽放大模式(Loose augmentation)
如果一个模块很大,必须分成几个部分,或者一个模块需要继承另一个模块,这时就有必要采用”放大模式”(augmentation)。
var module1 = (function (mod){ mod.m3 = function () { //... }; return mod; })(module1);
在浏览器环境中,模块的各个部分通常都是从网上获取的,有时无法知道哪个部分会先加载。如果采用上一节的写法,第一个执行的部分有可能加载一个不存在空对象,这时就要采用”宽放大模式”。
var module1 = ( function (mod){ //... return mod; })(window.module1 || {});
构造函数
使用new操作符完成的构造函数,比如:
var Person = function(name) { var name = name || 'leo'; // 私有变量,通过闭包访问 return { // 暴露公开的成员 setName: function (name) { name = name; }, getName: function () { return name; } };}var leo = new Person(); // Object{setName: function, getName: function()}console.log(leo.getName());
每次用的时候都要new一下,也就是说每个实例在内存里都是一份copy。
引用全局变量
JavaScript有一个特性叫做隐式全局变量,不管一个变量有没有用过,JavaScript解释器反向遍历作用域链来查找整个变量的var声明,如果没有找到var,解释器则假定该变量是全局变量,如果该变量用于了赋值操作的话,之前如果不存在的话,解释器则会自动创建它,这就是说在匿名闭包里使用或创建全局变量非常容易,不过比较困难的是,代码比较难管理,尤其是阅读代码的人看着很多区分哪些变量是全局的,哪些是局部的。
匿名函数里我们可以提供一个比较简单的替代方案,我们可以将全局变量当成一个参数传入到匿名函数然后使用,相比隐式全局变量,它又清晰又快,我们来看一个例子:
(function ($, YAHOO) { // 这里,我们的代码就可以使用全局的jQuery对象了,YAHOO也是一样} (jQuery, YAHOO));
基本的Module模式
有时候可能不仅仅要使用全局变量,而是也想声明全局变量,如何做呢?我们可以通过匿名函数的返回值来返回这个全局变量,这也就是一个基本的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扩展方式
Module模式的一个限制就是所有的代码都要写在一个文件,但是在一些大型项目里,将一个功能分离成多个文件是非常重要的,因为可以多人合作易于开发。再回头看看上面的全局参数导入例子,我们能否把blogModule自身传进去呢?答案是肯定的,我们先将blogModule传进去,添加一个函数属性,然后再返回就达到了我们所说的目的,上代码:
var blogModule = (function (my) { my.AddPhoto = function () { //添加内部代码 }; return my;} (blogModule));
但是有一个问题,这里扩展的方法,好像无法调用函数内部的私有变量,但是可以将一些需要的私有变量挂载到对象下。
松耦合扩展
上面的代码尽管可以执行,但是必须先声明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来用。
跨文件共享私有对象
如果一个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()”上锁”。
参考资料
- 玉伯: 前端模块化开发的价值
- 图灵社区: JavaScript模块化开发一瞥
- 深入理解JavaScript系列(3):全面解析Module模式
- Javascript模块化编程(一):模块的写法
- JavaScript模块化七日谈
原文: https://leohxj.gitbooks.io/front-end-database/content/javascript-modules/modules-intro.html
