Js模块模式
模块模式
索引
引子
这篇算是对第9篇中内容的发散和补充,当时我只是把模块模式中的一些内容简单的归为函数篇中去,在北川的提醒下,我才发觉这是非常不严谨的,于是我把这些内容拎出来,这就是这篇的由来。
什么是模块模式
在JavaScript中没有包(Package)的概念,而面对日益庞大的JavaScript代码,而这正促使模块化开发的迫切需求,所以也就诞生了JavaScript的模块模式, 最早这么叫的是老道,他称之为 模块模式 (Module Patterns).
模块模式提供了用于创建独立解耦的代码片段的工具,这些代码可以被当成黑盒,当你正在写的软件需求发生变化时,这些代码可以被添加、替换、移除。
模块模式是好几种模式的组合,他包括
- 命名空间模式
- 依赖声明模式
- 私有和特权成员模式
- 即时函数模式
下面我将结合例子来展开他的每个组成模式。
命名空间模式
模块化的目标是支持大规模的程序开发,处理分散代码块的组装,并能让代码正确的运行,哪怕包含了我们不期望出现的模块代码,也能正确的执行代码。 为了做到这点,不同的模块必须避免修改全局执行上下文,因此后续模块应当在他们所期望的作用域内执行。这实际上意味着模块应竟可能少的定义全局标识,理想情况是,所有的模块都定义在一个全局标识内。而命名空间就是一种非常好的解决方案。
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 ={};
}
我们发现每次添加新的模块前我们都要去检查这些模块是否已经存在(否则可能覆盖),以至于产生了大量的重复代码。所以这时我们需要一个很方便的处理命名空间细节的可重用函数,代码如下:
//定义一个全局标识
var GLOBAL = GLOBAL ||{};
//处理命名空间的函数
GLOBAL.namespace =function(str){
var arr = str.split("."),
o = GLOBAL,i;<br>
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中,我们也可以简单的自己去规范。代码如下:
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中:
//创建一个对象
varWeiRan={
name:"WeiRan",
getName:function(){
console.log(this.name);
}
}
//访问这个对象
console.log(WeiRan.name);//WeiRan
WeiRan.getName();//WeiRan
//通过构造函数来创建对象
functionPerson(name){
this.name = name;
this.getName =function(){
console.log(this.name);
}
}
//实例化一个对象
var wr =newPerson("WeiRan");
//访问这个实例
console.log(wr.name);//WeiRan
wr.getName();//WeiRan
我们发现无论我们是通过字面量的方式还是构造函数的方式创建对象,新建对象的所有属性和方法对外都是公开的,但是在很多场景下,我们并不想把所有的逻辑都暴露给调用者,这也就是说,我们需要私有的属性或方法,虽然JavaScript中没有针对私有成员的特俗语法,但是我们可以使用闭包来实现这种功能。代码如下:
functionPerson(UserName){
//私有属性
var name =UserName;
//API
this.getName =function(){
console.log(name);
}
}
//实例化一个对象
var wr =newPerson("WeiRan");
//访问对象
console.log(wr.name);//undefined
wr.getName();//WeiRan
我们知道,局部变量只有在特定的作用域下才能访问到,比如Person方法内部定义的局部变量name只有在Person内才能访问,这恰好符合了我们私有成员的要求,我们通过闭包对外公开了一个借口(getName),外部想要访问wr的name只能通过getName去访问。而类似getName这样对外公布的方法我们称之为特权方法(Privileged Method),我更喜欢叫他对外接口。
特权方法特别要注意的是,为了保证整个方法的私有成员,不要返回对整个方法的引用,这样外部也就能随便的通过这个引用来访问所有的私有成员,这也就没有任何私有性可言了。
即时函数
在上面的代码中,我们搭起了一个简单的模块架子,但是在通常的场景下,我们会遇到一些问题,实例如下:
//定义一个全局标识
var GLOBAL = GLOBAL ||{};
//处理命名空间的函数
GLOBAL.namespace =function(str){
var arr = str.split("."),
o = GLOBAL,<br> 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的具体实现逻辑也保持私有,那么使用揭示模块模式就再合适不过了。
揭示模块模式
揭示模块模式是对模块模式中私有性保护的升级,就拿我以前的一段代码为例,详细代码如下:
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模式
Js模块模式的更多相关文章
- JavaScript设计模式-单例模式、模块模式(转载 学习中。。。。)
(转载地址:http://technicolor.iteye.com/blog/1409656) 之前在<JavaScript小特性-面向对象>里面介绍过JavaScript面向对象的特性 ...
- JS模块之AMD, CMD, CommonJS、UMD和ES6模块
CommonJS 传送门 同步加载,适合服务器开发,node实现了commonJS.module.exports和require 判断commonJS环境的方式是(参考jquery源码): if ( ...
- JS之模块模式应用
之前做过一些简单的单页面应用项目,是对模块模式很好的应用,我决定动手做一个简单的Demo出来. 基本思想是设计一个加载器,当用户点击菜单时,获取不同选项的按钮id,根据不同id实现对页面内容的替换. ...
- JS通用模块模式 UMD
历史 JS诞生之初面向简单页面开发, 没有模块的概念. 后来页面逐渐复杂, 人类构造到 IIFE 立即执行函数来模拟 模块: 之前也有雅虎的实践,使用命名空间 作为模块名. 最后衍生出 面向各种使用场 ...
- js精要之模块模式
// 模块模式是一种用于创建拥有私有数据的单件对象的模式,基本做法是使用立调函数(IIFE)来返回一个对象 var yourObjet = (function(){ // 私有数据 return { ...
- JS 设计模式四 -- 模块模式
概念 模块模式的思路 就是 就是单例模式添加私有属性和私有方法,减少全局变量的使用. 简单的代码结构: var singleMode = (function(){ // 创建私有变量 var priv ...
- [Js代码风格]浅析模块模式
1.实例解释模块模式 简明扼要的说,经典的模块模式指的定义一个立即执行的匿名函数.在函数中定义私有函数和私有变量并且返回一个包含公共变量和公共函数作为属性和方法的匿名对象. var classicMo ...
- js模块开发(一)
现在嵌入页面里面的javascript代码越来越复杂,于是可能依赖也越来越严重,使用别人开发的js也越来越多,于是在理想情况下,我们只需要实现核心的业务逻辑,其他都可以加载别人已经写好的模块. 于是j ...
- 初涉JavaScript模式 (11) : 模块模式
引子 这篇算是对第9篇中内容的发散和补充,当时我只是把模块模式中的一些内容简单的归为函数篇中去,在北川的提醒下,我才发觉这是非常不严谨的,于是我把这些内容拎出来,这就是这篇的由来. 什么是模块模式 在 ...
随机推荐
- OBIEE SampleAppv406 自己主动启动配置
SampleApp 一个简短的引论: SampleApp这是一个一站式服务,几乎证明OBIEE不管顶的特征可想而知. 它安装了大量的应用(数据库,OBIEE,的Endeca.TimesTen的.Ess ...
- 网络资源(9) - TDD视频
2014_08_26 http://v.youku.com/v_show/id_XMzI4Mzk1MjQ4.html TDD测试驱动开发
- 【从翻译mos文章】oracle linux 和外部存储系统 关系
oracle linux 和外部存储系统 关系 参考原始: Oracle Linux and External Storage Systems (Doc ID 753050.1) 范围: Linux ...
- hdu1286 寻找新朋友 (欧拉功能)
原标题:点击打开链接 关于欧拉函数的算法具体解说:点击打开链接 欧拉函数 1.欧拉函数是不全然积性函数. 2.欧拉函数p(x) = x * (p1 - 1) / p1 * (p2 - 1)/p2 * ...
- EF中的贪婪加载和延迟加载(懒加载)
在上一章中,我们使用了Linq对Entity Framework进行了一个查询,但是通过学习我们却发现了懒加载给我来的性能上的开销是很到的,尤其是在循环中,如果数据量不是很多的情况下还可以接受,如果数 ...
- SOA一些资料
SOA相关资料整理分享 2015-03-26 16:16 by 蘑菇先生, 693 阅读, 9 评论, 收藏, 编辑 昨@幸福框架同学问能否推荐SOA一些资料.想想之前看过不少资料文档,就整理分享下. ...
- UIButton UIImage 用法分析
一.UIButton和UIImageView的区别 1.显示图片 1> UIImageView只能显示一种图片(图片默认会填充整个UIImageView) image\setImage: 2&g ...
- 通过扩展改善ASP.NET MVC的验证机制[使用篇]
原文:通过扩展改善ASP.NET MVC的验证机制[使用篇] ASP.NET MVC提供一种基于元数据的验证方式是我们可以将相应的验证特性应用到作为Model实体的类型或者属性/字段上,但是这依然具有 ...
- c语言中逗号运算符和逗号表达式
原文:c语言中逗号运算符和逗号表达式 C语言提供一种特殊的运算符——逗号运算符.用它将两个表达式连接起来.如: 3+5,6+8称为逗号表达式,又称为“顺序求值运算符”.逗号表达式的一般形式为 表达式1 ...
- 在Installshield的安装进度中显示自己设置的信息
原文:在Installshield的安装进度中显示自己设置的信息 以Installscript msi project为例,在installshield所制作的安装包安装过程中显示安装进度的,就在On ...