引子

这篇算是对第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. 【转】Android低功耗蓝牙应用开发获取的服务UUID

    原文网址:http://blog.csdn.net/zhangjs0322/article/details/39048939 Android低功耗蓝牙应用程序开始时获取到的蓝牙血压计所有服务的UUID ...

  2. CodeForces 588A

    题目链接: http://codeforces.com/problemset/problem/588/A 解题思路: 这个题目很简单, 就是前一天肉的价格比后面几天低还是高,如果是高的话,只要买当天份 ...

  3. Template 使用注意问题和范例

    1. 基本定义 模板是 2. 分类 2.1 函数模板 (1) 作用:  函数模板可以用来创建一个通用功能的函数,以支持多种不同形参,进一步简化重载函数的函数体设计. (2)申明方法: template ...

  4. maven上传自定义jar到本地仓库

    mvn install:install-file  -Dfile=D:/baidu/ueditor-1.1.1.jar  -DgroupId=com.baidu.ueditor  -Dartifact ...

  5. linux —— 学习笔记(软件操作:安装、卸载、执行)

    目录: 0.相关基本命令    1.安装软件    2.卸载软件    3.打开软件  0.相关基本命令 与软件操作相关的主要命令有:dpkg  和 apt-get . dpkg   : “dpkg ...

  6. mvc与mvvm

    mvc:被动型式的,也就是说,只有view有要求的时候,控制器才有反应 View     Controller       Model 可以把一个页面看作是由多个view组成 Controller控制 ...

  7. Apache XAMPP Fails to start under Windows XP

    Apache XAMPP Fails to start under Windows XP I’ve been installing XAMPP a hundred times before since ...

  8. nvl,空时的推断和取值

    nvl NVL的概念 Oracle/PLSQL中的一个函数. 格式为: NVL( string1, replace_with) 功能:假设string1为NULL,则NVL函数返回replace_wi ...

  9. SAP OTR 字段维护 更改SAP的字段翻译

    维护系统文本字段:SOTR_EDIT           TC:SE63 在SAP用户选择屏幕中,用鼠标选定一个栏位后按F1键,能够看到SAP对其详细解释,通常这样的解释文本分为两部分,一部分为标题, ...

  10. PHP安全编程:留心后门URL 直接可以通过URL访问(转)

    后门URL是指虽然无需直接调用的资源能直接通过URL访问.例如,下面WEB应用可能向登入用户显示敏感信息: <?php $authenticated = FALSE; $authenticate ...