引子

这篇算是对第9篇中内容的发散和补充,当时我只是把模块模式中的一些内容简单的归为函数篇中去,在北川的提醒下,我才发觉这是非常不严谨的,于是我把这些内容拎出来,这就是这篇的由来。

什么是模块模式

在JavaScript中没有包(Package)的概念,而面对日益庞大的JavaScript代码,而这正促使模块化开发的迫切需求,所以也就诞生了JavaScript的模块模式, 最早这么叫的是老道,他称之为 模块模式 (Module Patterns).

模块模式提供了用于创建独立解耦的代码片段的工具,这些代码可以被当成黑盒,当你正在写的软件需求发生变化时,这些代码可以被添加、替换、移除。

模块模式是好几种模式的组合,他包括

  • 命名空间模式
  • 依赖声明模式
  • 私有和特权成员模式
  • 即时函数模式

下面我将结合例子来展开他的每个组成模式。

命名空间模式

模块化的目标是支持大规模的程序开发,处理分散代码块的组装,并能让代码正确的运行,哪怕包含了我们不期望出现的模块代码,也能正确的执行代码。 为了做到这点,不同的模块必须避免修改全局执行上下文,因此后续模块应当在他们所期望的作用域内执行。这实际上意味着模块应竟可能少的定义全局标识,理想情况是,所有的模块都定义在一个全局标识内。而命名空间就是一种非常好的解决方案。

JavaScript 中没有命名空间的概念,但是这种特征是非常好实现的。可以创建一个全局对象,然后将所有的功能模块添加到该全局对象中去,从而在具有大量函数、对象和其他变量的情况下不会污染全局范围。

随着功能模块的增加,如下所示:

```javascript
//添加一个模块
var module = module || {};
//在module里面添加一个模块
if (!typeof module === "undefined") {
module.module1 = {};
} else {
var module = {};
module.module1 = {};
}
//在module1里面添加一个模块
if (!typeof module === "undefined") {
if (!typeof module.module1 === "undefined") {
module.module1.module2 = {};
} else {
module.module1 = {};
module.module1.module2 = {};
}
} else {
var module = {};
module.module1 = {};
module.module1.module2 = {};
}
```

我们发现每次添加新的模块前我们都要去检查这些模块是否已经存在(否则可能覆盖),以至于产生了大量的重复代码。所以这时我们需要一个很方便的处理命名空间细节的可重用函数,代码如下:

```javascript
//定义一个全局标识
var GLOBAL = GLOBAL || {};
//处理命名空间的函数
GLOBAL.namespace = function(str) {
var arr = str.split("."),
o = GLOBAL,i;
            
for (i = (arr[0] == "GLOBAL") ? 1 : 0; i < arr.length; i++) {
o[arr[i]] = o[arr[i]] || {};
o = o[arr[i]];
}
}
//注册一个模块(即给一个模块划分一个命名空间)
GLOBAL.namespace("Module1");
//给Module1添加一个print方法
GLOBAL.Module1.print = function(msg){
console.log(msg);
}
//调用Module1的print函数
GLOBAL.Module1.print("1"); //1
//也可以这样调用
GLOBAL.Module1.print.call(null, "3"); //3
```

这个实现是非破坏性的,也就是说,如果这个属性已经存在,则不会再重新创建。我们只需要在每次创建模块前先注册下,而不需要去管他是否存在,这样一来大大减少了代码量。

声明依赖

这和Java中的依赖注入很像,一个功能类不可能单独存在,他需要一些“基础设施”,例如我们要吃核桃,我们需要一个工具去打开它,而这个工具就是我们吃核桃的基础设施,也叫做依赖。在程序中,我们做的通常是导入其他的支持模块也就是依赖,从而避免了我们重复去写那些简单的基础逻辑。

在其他语言中,都有专门的语法结构去实现,而在JavaScript中,我们也可以简单的自己去规范。代码如下:

```javascript
GLOBAL.namespace("Nut");
//给“核桃” 添加一个吃的方法
GLOBAL.Nut.eat = function(aNut){
//依赖
var tools = GLOBAL.Tools.spanner;
//用扳子砸核桃
tools.use.call(aNut);
}
```

这是一段没有实际意义的代码,只是用来说明依赖,其中的扳子就是依赖,我们使用他来吃核桃,在程序中,我们就要导入这个“板子”。

虽然很简单,但是这样做有很多好处:

  • 在函数顶部的声明可以让我们很容易发现并解析依赖
  • 解析局部变量的速度总比解析全局变量要快
  • 某些压缩工具可以对其进行简化,减少了字节数(工具会对局部变量进行简化,而全局变量则不可以)

私有和特权成员

在其他oo语言中,都有类似private,public,interface这样的语法来区别公共属性,私有属性,公共接口。但是JavaScript没有(都要我们自己去实现,这也是我热爱JS的原因之一)。在JavaScript中所有对象的成员都是公开的。在JavaScript中:

```javascript
//创建一个对象
var WeiRan = {
name: "WeiRan",
getName: function() {
console.log(this.name);
}
}
//访问这个对象
console.log(WeiRan.name); //WeiRan
WeiRan.getName(); //WeiRan
//通过构造函数来创建对象
function Person(name) {
this.name = name;
this.getName = function() {
console.log(this.name);
}
}
//实例化一个对象
var wr = new Person("WeiRan");
//访问这个实例
console.log(wr.name); //WeiRan
wr.getName(); //WeiRan
```

我们发现无论我们是通过字面量的方式还是构造函数的方式创建对象,新建对象的所有属性和方法对外都是公开的,但是在很多场景下,我们并不想把所有的逻辑都暴露给调用者,这也就是说,我们需要私有的属性或方法,虽然JavaScript中没有针对私有成员的特俗语法,但是我们可以使用闭包来实现这种功能。代码如下:

```javascript
function Person(UserName) {
//私有属性
var name = UserName;
//API
this.getName = function() {
console.log(name);
}
}
//实例化一个对象
var wr = new Person("WeiRan");
//访问对象
console.log(wr.name); //undefined
wr.getName(); //WeiRan
```

我们知道,局部变量只有在特定的作用域下才能访问到,比如Person方法内部定义的局部变量name只有在Person内才能访问,这恰好符合了我们私有成员的要求,我们通过闭包对外公开了一个借口(getName),外部想要访问wr的name只能通过getName去访问。而类似getName这样对外公布的方法我们称之为特权方法(Privileged Method),我更喜欢叫他对外接口。

特权方法特别要注意的是,为了保证整个方法的私有成员,不要返回对整个方法的引用,这样外部也就能随便的通过这个引用来访问所有的私有成员,这也就没有任何私有性可言了。

即时函数

在上面的代码中,我们搭起了一个简单的模块架子,但是在通常的场景下,我们会遇到一些问题,实例如下:

```javascript
//定义一个全局标识
var GLOBAL = GLOBAL || {};
//处理命名空间的函数
GLOBAL.namespace = function(str) {
var arr = str.split("."),
o = GLOBAL,
                i;
for (i = (arr[0] == "GLOBAL") ? 1 : 0; i < arr.length; i++) {
o[arr[i]] = o[arr[i]] || {};
o = o[arr[i]];
}
}
//注册一个模块
GLOBAL.namespace("Module");
//字面量方式
GLOBAL.Module.console = {
msg : "WeiRan",
log : function(){
console.log(this.msg);
}
}
GLOBAL.Module.console.log(); //WeiRan
console.log(GLOBAL.Module.console.msg); //WeiRan
//即时函数方式
GLOBAL.Module.console1 = (function(){
var msg = "WeiRan";
return{
log : function(){
console.log(msg);
}
}
}());
GLOBAL.Module.console1.log(); //WeiRan
console.log(GLOBAL.Module.console1.msg); //undefined
```

在上面我列举了两种编写模块的方式,第一种字面量的形式完全不能保证私有属性,他的所有成员都是公开的,第二种,我们通过即时函数提供的私有作用域保证了模块私有成员的私有性,在最后返回对象了一个对象,该对象包含该模块的公共API。

而对于返回的对象,如果我们还要对公共API的具体实现逻辑也保持私有,那么使用揭示模块模式就再合适不过了。

揭示模块模式

揭示模块模式是对模块模式中私有性保护的升级,就拿我以前的一段代码为例,详细代码如下:

```javascript
GLOBAL.comm.comnav = (function() {
var bindNav = function(navList) {
$.each(navList, function(key, val) {
$("#" + val.split("-")[0] + ">span").click(function() {
$(this).siblings().each(function() {
$(this).find(".active").hide().siblings().find("[_tag=" + $(this).attr("_tag") + "]").removeClass("red");
});
$(this).find(".active").show().siblings().find("[_tag=" + $(this).attr("_tag") + "]").addClass("red");
showTag([val.split("-")[1], val.split("-")[2]], $(this).attr("_tag"));
});
});
};
var processNav = function(navId, commId, listId) {
return navId + "-" + commId + "-" + listId;
};
var showTag = function(tagList, tagName) {
$.each(tagList, function(index) {
$("#" + tagList[index]).find("[_tag=" + tagName + "]").show().siblings().hide();
});
};
return {
init: bindNav,
newInstantce: processNav
}
})();
```

在上面的示例中,我在模块内部定义了三个私有方法(注意是以函数表达式的形式定义的),我没有把API函数的具体逻辑显式的写在返回的对象中,我是以函数名的方式传递了一个私有方法的引用给API函数,这样我们就连模块暴露的API方法都进行了处理,这样才算真正的接口(只有声明,没有实现)

结语

由于最近工作和过年的关系,断了好长时间,自己也意识到自己懈怠了,新的一年开始了,还有太多东西等着我去学,2014就是一个字,干!

如果你在文中发现错误或则你觉得不正确的地方,希望你的指正。

初涉JavaScript模式 (11) : 模块模式的更多相关文章

  1. javascript模式之模块模式

    使用模式来组织代码有很多优点:使代码的结构更清晰,逻辑性更强,更容易维护.还可以避免很多错误. 首先,在javascript主要分为两大类: 编程模式-- 一些专门为javascript语言开发出的最 ...

  2. JavaScript基础对象创建模式之模块模式(Module Pattern)(025)

    模块模式可以提供软件架构,为不断增长的代码提供组织形式.JavaScript没有提供package的语言表示,但我们可以通过模块模式来分解并组织 代码块,这些黑盒的代码块内的功能可以根据不断变化的软件 ...

  3. 对象创建模式之模块模式(Module Pattern)

    模块模式可以提供软件架构,为不断增长的代码提供组织形式.JavaScript没有提供package的语言表示,但我们可以通过模块模式来分解并组织代码块,这些黑盒的代码块内的功能可以根据不断变化的软件需 ...

  4. 再起航,我的学习笔记之JavaScript设计模式11(外观模式)

    经过一段时间的学习与分享,我们对创建型设计模式已经有了一定的认识,未来的一段时间里我们将展开新的篇章,开始迈入结构性设计模式的学习. 结构性设计模式与创建型设计模式不同,结构性设计模式更偏向于关注如何 ...

  5. JavaScript设计模式-11.桥梁模式

    <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title> ...

  6. Js模块模式

    模块模式 索引 引子 什么是模块模式 命名空间模式 声明依赖 私有和特权成员 即时函数 揭示模块模式 结语 引子 这篇算是对第9篇中内容的发散和补充,当时我只是把模块模式中的一些内容简单的归为函数篇中 ...

  7. javascript 设计模式-----模块模式

    在一些大的项目中经常使用到模块,在这里,我们将了解一下什么是模块模式.模块模式最简单的方法大家一定会用过,如下所示: var a = { b : 1, c : 2 } 这样一个对象的直接量其实就已经是 ...

  8. php在apache中一共有三种工作方式:CGI模式、FastCGI模式、Apache 模块DLL

    php在apache中一共有三种工作方式:CGI模式.FastCGI .FastCGI是什么? FastCGI是语言无关的.可伸缩架构的CGI开放扩展,其主要行 为是将CGI解释器进程保持在内存中并因 ...

  9. JavaScript基础对象创建模式之命名空间(Namespace)模式(022)

    JavaScript中的创建对象的基本方法有字面声明(Object Literal)和构造函数两种,但JavaScript并没有特别的语法来表示如命名空间.模块.包.私有属性.静态属性等等面向对象程序 ...

随机推荐

  1. 【转】JAVA程序中Float和Double精度丢失问题

    原文网址:http://blog.sina.com.cn/s/blog_827d041701017ctm.html 问题提出:12.0f-11.9f=0.10000038,"减不尽" ...

  2. 【扩展欧几里得】BAPC2014 I Interesting Integers (Codeforces GYM 100526)

    题目链接: http://codeforces.com/gym/100526 http://acm.hunnu.edu.cn/online/?action=problem&type=show& ...

  3. mysql集群安装(centos)

    mysql cluster : 1. 基于NDB Cluster 的分布式数据库系统 2. mysql集群中各服务器节点不共享数据 3. 在mysql cluster中节点指的是进程,区别于其他的集群 ...

  4. qt项目转vs项目

    Qt creator是一个非常好用的跨平台项目管理工具和集成开发环境(IDE).但是对于我自己来说Visual Studio依然是我最顺手的开发工具,由于Qt使用了moc,这样要是自己管理Visual ...

  5. [Java] Map / HashMap - 源代码学习笔记

    Map 1. 用于关联 key 和 value 的对象,其中 key 与 key 之间不能重复. 2. 是一个接口,用来代替 Java 早期版本中的 Dictionary 抽象类. 3. 提供三种不同 ...

  6. Power Calculus 快速幂计算 (IDA*/打表)

    原题:1374 - Power Calculus 题意: 求最少用几次乘法或除法,可以从x得到x^n.(每次只能从已经得到的数字里选择两个进行操作) 举例: x^31可以通过最少6次操作得到(5次乘, ...

  7. 常考的算法及Java知识总结

    算法 1 字符串模式匹配问题 2 排列组合问题 3 查找排序问题 数据结构 B树(B,B*,B+,红黑树)和二叉树的区别,MAP,hashmap, JAVA: 线程sleep,wait,wake(), ...

  8. Java调用R(三)_系统命令调用

    java通过配置的系统命令Rscript直接调用R脚本. 优点:R脚本和Java代码完全分离 缺点:R中变量不能控制 1. Java本地能够成功调用. public void CallR() { Ru ...

  9. Redis的Java客户端Jedis的八种调用方式(事务、管道、分布式)介绍

    jedis是一个著名的key-value存储系统,而作为其官方推荐的java版客户端jedis也非常强大和稳定,支持事务.管道及有jedis自身实现的分布式. 在这里对jedis关于事务.管道和分布式 ...

  10. Lucene实例教程

    Lucene是apache组织的一个用java实现全文搜索引擎的开源项目. 其功能非常的强大,api也很简单.总得来说用Lucene来进行建立 和搜索和操作数据库是差不多的(有点像),Document ...