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);//WeiRanWeiRan.getName();//WeiRan//通过构造函数来创建对象functionPerson(name){this.name = name;this.getName =function(){console.log(this.name);}}//实例化一个对象var wr =newPerson("WeiRan");//访问这个实例console.log(wr.name);//WeiRanwr.getName();//WeiRan
我们发现无论我们是通过字面量的方式还是构造函数的方式创建对象,新建对象的所有属性和方法对外都是公开的,但是在很多场景下,我们并不想把所有的逻辑都暴露给调用者,这也就是说,我们需要私有的属性或方法,虽然JavaScript中没有针对私有成员的特俗语法,但是我们可以使用闭包来实现这种功能。代码如下:
functionPerson(UserName){//私有属性var name =UserName;//APIthis.getName =function(){console.log(name);}}//实例化一个对象var wr =newPerson("WeiRan");//访问对象console.log(wr.name);//undefinedwr.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();//WeiRanconsole.log(GLOBAL.Module.console.msg);//WeiRan//即时函数方式GLOBAL.Module.console1 =(function(){var msg ="WeiRan";return{log :function(){console.log(msg);}}}());GLOBAL.Module.console1.log();//WeiRanconsole.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篇中内容的发散和补充,当时我只是把模块模式中的一些内容简单的归为函数篇中去,在北川的提醒下,我才发觉这是非常不严谨的,于是我把这些内容拎出来,这就是这篇的由来. 什么是模块模式 在 ...
随机推荐
- 解决RecyclerView无法onItemClick问题
供RecyclerView采用.会员可以查看将替代ListView的RecyclerView 的使用(一),单单从代码结构来说RecyclerView确实比ListView优化了非常多.也简化了我们编 ...
- Mybatis 构造resultMap 搜sql
映射配置文件 <!-- type:映射数据类型的实体类 id:resultMap的唯一标识 --> <resultMap type="person" id=&qu ...
- C_文件读写流
strcmp() 所在头文件:string.h 功能:比较俩个字符串 一般形式:strcmp(字符串1,字符串2) 说明: 当S1<S2时,返回为负数return result,result&l ...
- rpm安装FAQ
1.error: cannot create %sourcedir /usr/src/redhat/SOURCES错误的解决方案 显现error: cannot create %sourcedir / ...
- Java实现缓存(类似于Redis)
Java实现缓存,类似于Redis的实现,可以缓存对象到内存中,提高访问效率.代码如下: import java.util.ArrayList; import java.util.HashMap; i ...
- 超酷的jQuery百叶窗图片滑块实现教程
原文:超酷的jQuery百叶窗图片滑块实现教程 今天我们要来分享一款基于jQuery的百叶窗焦点图插件,也可以说是图片滑块插件.这种jQuery焦点图插件的应用非常广泛,在早些年前,我们需要用flas ...
- 原生AJAX基础讲解及兼容处理
原文:原生AJAX基础讲解及兼容处理 AJAX = Asynchronous JavaScript and XML (异步的JavaScript和XML). AJAX不是新技术 ,但却是热门的技术.它 ...
- AngularJS + CoffeeScript
AngularJS + CoffeeScript 前端开发环境配置详解 AngularJS 号称 '第一框架' ('The first framework') 确实是名不虚传.由其从jQuery中完全 ...
- Spring之SpringMVC前端控制器DispatcherServlet(源码)分析
1.DispatcherServlet作用说明 DispatcherServlet提供Spring Web MVC的集中访问点,而且负责职责的分派,而且与Spring IoC容器无缝集成,从而可以获得 ...
- Java程序员应该知道的10个Eclipse调试技巧
Eclipse是众多Java程序员实用的开发工具,其中开发技巧也是繁多,但作为优秀的Java程序员,需要掌握最起码的调试技巧. 1 条件断点 2 异常断点 3 监视点 4 评估/检查 5 修改变量值 ...