Js模块模式

时间:2023-03-08 18:07:49

模块模式

索引

引子

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

什么是模块模式

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

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

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

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

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

命名空间模式

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

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

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

  1. //添加一个模块
  2. var module = module ||{};
  3. //在module里面添加一个模块
  4. if(!typeof module ==="undefined"){
  5. module.module1 ={};
  6. }else{
  7. var module ={};
  8. module.module1 ={};
  9. }
  10. //在module1里面添加一个模块
  11. if(!typeof module ==="undefined"){
  12. if(!typeof module.module1 ==="undefined"){
  13. module.module1.module2 ={};
  14. }else{
  15. module.module1 ={};
  16. module.module1.module2 ={};
  17. }
  18. }else{
  19. var module ={};
  20. module.module1 ={};
  21. module.module1.module2 ={};
  22. }

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

  1. //定义一个全局标识
  2. var GLOBAL = GLOBAL ||{};
  3. //处理命名空间的函数
  4. GLOBAL.namespace =function(str){
  5. var arr = str.split("."),
  6. o = GLOBAL,i;<br>            
  7. for(i =(arr[0]=="GLOBAL")?1:0; i < arr.length; i++){
  8. o[arr[i]]= o[arr[i]]||{};
  9. o = o[arr[i]];
  10. }
  11. }
  12. //注册一个模块(即给一个模块划分一个命名空间)
  13. GLOBAL.namespace("Module1");
  14. //给Module1添加一个print方法
  15. GLOBAL.Module1.print =function(msg){
  16. console.log(msg);
  17. }
  18. //调用Module1的print函数
  19. GLOBAL.Module1.print("1");//1
  20. //也可以这样调用
  21. GLOBAL.Module1.print.call(null,"3");//3

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

声明依赖

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

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

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

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

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

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

私有和特权成员

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

  1. //创建一个对象
  2. varWeiRan={
  3. name:"WeiRan",
  4. getName:function(){
  5. console.log(this.name);
  6. }
  7. }
  8. //访问这个对象
  9. console.log(WeiRan.name);//WeiRan
  10. WeiRan.getName();//WeiRan
  11. //通过构造函数来创建对象
  12. functionPerson(name){
  13. this.name = name;
  14. this.getName =function(){
  15. console.log(this.name);
  16. }
  17. }
  18. //实例化一个对象
  19. var wr =newPerson("WeiRan");
  20. //访问这个实例
  21. console.log(wr.name);//WeiRan
  22. wr.getName();//WeiRan

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

  1. functionPerson(UserName){
  2. //私有属性
  3. var name =UserName;
  4. //API
  5. this.getName =function(){
  6. console.log(name);
  7. }
  8. }
  9. //实例化一个对象
  10. var wr =newPerson("WeiRan");
  11. //访问对象
  12. console.log(wr.name);//undefined
  13. wr.getName();//WeiRan

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

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

即时函数

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

  1. //定义一个全局标识
  2. var GLOBAL = GLOBAL ||{};
  3. //处理命名空间的函数
  4. GLOBAL.namespace =function(str){
  5. var arr = str.split("."),
  6. o = GLOBAL,<br>                i;
  7. for(i =(arr[0]=="GLOBAL")?1:0; i < arr.length; i++){
  8. o[arr[i]]= o[arr[i]]||{};
  9. o = o[arr[i]];
  10. }
  11. }
  12. //注册一个模块
  13. GLOBAL.namespace("Module");
  14. //字面量方式
  15. GLOBAL.Module.console ={
  16. msg :"WeiRan",
  17. log :function(){
  18. console.log(this.msg);
  19. }
  20. }
  21. GLOBAL.Module.console.log();//WeiRan
  22. console.log(GLOBAL.Module.console.msg);//WeiRan
  23. //即时函数方式
  24. GLOBAL.Module.console1 =(function(){
  25. var msg ="WeiRan";
  26. return{
  27. log :function(){
  28. console.log(msg);
  29. }
  30. }
  31. }());
  32. GLOBAL.Module.console1.log();//WeiRan
  33. console.log(GLOBAL.Module.console1.msg);//undefined

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

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

揭示模块模式

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

  1. GLOBAL.comm.comnav =(function(){
  2. var bindNav =function(navList){
  3. $.each(navList,function(key, val){
  4. $("#"+ val.split("-")[0]+">span").click(function(){
  5. $(this).siblings().each(function(){
  6. $(this).find(".active").hide().siblings().find("[_tag="+ $(this).attr("_tag")+"]").removeClass("red");
  7. });
  8. $(this).find(".active").show().siblings().find("[_tag="+ $(this).attr("_tag")+"]").addClass("red");
  9. showTag([val.split("-")[1], val.split("-")[2]], $(this).attr("_tag"));
  10. });
  11. });
  12. };
  13. var processNav =function(navId, commId, listId){
  14. return navId +"-"+ commId +"-"+ listId;
  15. };
  16. var showTag =function(tagList, tagName){
  17. $.each(tagList,function(index){
  18. $("#"+ tagList[index]).find("[_tag="+ tagName +"]").show().siblings().hide();
  19. });
  20. };
  21. return{
  22. init: bindNav,
  23. newInstantce: processNav
  24. }
  25. })();

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

结语

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

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

作者:未然丶

JavaScript模式

摘要: 引子这篇算是对第9篇中内容的发散和补充,当时我只是把模块模式中的一些内容简单的归为函数篇中去,在北川的提醒下,我才发觉这是非常不严谨的,于是我把这些内容拎出来,这就是这篇的由来。什么是模块模式在JavaScript中没有包(Package)的概念,而面对日益庞大的JavaScript代码,而这正促使模块化开发的迫切需求,所以也就诞生了JavaScript的模块模式, 最早这么叫的是老道,他称之为 模块模式 (Module Patterns).模块模式提供了用于创建独立解耦的代码片段的工具,这些代码可以被当成黑盒,当你正在写的软件需求发生变化时,这些代码可以被添加、替换、移除。模块模式是好几种模阅读全文
posted @ 2014-02-19 14:39 未然丶 阅读(509) | 评论 (3) 编辑
摘要: 写在前面 不知不觉写到第10篇了。这篇写起来很忐忑,终于和高级搭上边了(呵呵),这篇我们 主要 说一下 JS 方法的部分高级用法(我知道的),笔者水平有限,难免有错。废话不多少,进入正文。 初始化 我们在看一些别人写的优秀的代码,特别是组件时,我们经常能发现有init或initializa这样的方法,它一般执行的都是初始化。那初始化一般都有几种呢,我们来一一介绍: 初始化对象 初始化对象...阅读全文
posted @ 2014-01-26 17:30 未然丶 阅读(179) | 评论 (2) 编辑
摘要: 回调模式 上一篇,对JavaScript函数进行了大体的介绍,这一篇对一些在工作中经常遇到的情况进行扩展。 在工作中,我们经常遇到很多需求,比如现在有一个需求: 一栋10层的大楼,当我们在坐电梯时,电梯每上一层,每层的电梯显示屏上即时显示电梯当前所在的楼层。 这样我们可能不到1s,就想到了解决方案,只要电梯每上一层,把每一层的电梯显示屏数字 +1 ,代码核心如下: ```javascr...阅读全文
posted @ 2014-01-22 05:51 未然丶 阅读(103) | 评论 (4) 编辑
摘要: 什么是函数 函数,是一个大型程序中的某部份代码,由一个或多个语句块组成。它负责完成某项特定任务,而且相较于其他代码,具备相对的独立性。(*) 函数的特点 第一类对象 在JavaScript世界中函数是一等公民,它不仅拥有一切传统函数的使用方式(声明和调用),而且可以做到像简单值一样赋值、传参、返回,这样的函数也称之为第一级函数(First-class Function)。不仅如此,J...阅读全文
posted @ 2014-01-16 01:05 未然丶 阅读(166) | 评论 (2) 编辑
摘要: 组合使用构造函数模式和原型模式 上篇,我们提到了原型模式的缺点,就是每个实例不能拥有自己的属性,因为纯原型模式所有的属性都是公开给每个实例的,故我们可以组合使用构造函数模式和原型模式。构造函数用来定义实例的属性,而原型模式用来定义方法和公用属性。这样的话,每个实例都有自己的属性副本(而不是指向原型的引用),同时它也共享原型上方法的引用。而且,这种混合模式还支持对构造函数传参,所以可以说是结合了两种...阅读全文
posted @ 2014-01-13 03:04 未然丶 阅读(118) | 评论 (0) 编辑
摘要: 原型与in操作符 有两种方式使用in操作符:单独使用和在for-in循环中使用。 在单独使用时,in操作符会遍历实例公开(可枚举)的属性,如果找到该指定属性则返回true,无论该指定属性是存在与实例中还是原型中。直接上代码: ```javascript function Animal(){ } Animal.prototype...阅读全文
posted @ 2014-01-12 05:42 未然丶 阅读(128) | 评论 (0) 编辑
摘要: 什么是字面量? 在编程语言中,字面量是一种表示值的记法。例如,"Hello, World!" 在许多语言中都表示一个字符串字面量(string literal ),JavaScript也不例外。比如举几个JavaScript字面量的例子,如5、true、false和null,它们分别表示一个整数、两个布尔值和一个空对象。 咋一看,可能很绕,字面量很难下定义,其实以我的理解就是一眼能理解的量就是...阅读全文
posted @ 2014-01-02 02:16 未然丶 阅读(136) | 评论 (0) 编辑
摘要: 尽量少用全局变量 大量使用全局变量会导致的后果 全局变量创建以后会在整个JavaScript应用和Web页面*享。所有的全局变量都存在于一个全局命名空间内,很容易发生冲突 不知不觉创建了全局变量 其原因在于JavaScript的两个特性,第一个是JavaScript可直接使用变量而无需声明,第二个是JavaScript的暗示全局变量的概念,即任何变量如果未经声明,就为全局对象所有 ...阅读全文
posted @ 2014-01-01 02:59 未然丶 阅读(154) | 评论 (3) 编辑
摘要: 什么是模式? 广义上的模式是指 :在物体或事件上,产生的一种规律变化与自我重复的样式与过程。在模式之中,某些固定的元素不断以可预测的方式周期性重现。最基本而常见的模式,称为密铺,具备重复性以及周期性两大特征。找寻出固定模式是人类基本的认知功能之一。(从wiki百科抄的) 碉堡了,啥玩意这么绕。。。而在我们软件的开发过程中,模式通常可以理解为解决一类问题的方法或方案。(我的理解是解决一些具有共同点的问题的方法) 模式有什么好处? 我们在采用一种新的技术或者语言之前,会衡量他对我们目前乃至以后的开发会产生什么后果,是好的还是坏的。通过学习模式,我们可以使用经过实践证明有效的经验来编写代码,而无需去阅读全文
posted @ 2013-12-30 16:21 未然丶 阅读(170) | 评论 (1) 编辑