JavaScript设计模式-单例模式、模块模式

之前在《JavaScript小特性-面向对象》里面介绍过JavaScript面向对象的特性了,有了面向对象之后,自然就会想——那是不是还有设计模式呢?由于js面向对象的方式是基于原型(prototype)的,而不是传统基于类型(class),所以js的设计模式也和经典的设计模式有些差异。

 

关于设计模式

先说说什么是设计模式吧。很多人都觉得“设计模式”这东西很玄乎,把Gof四人帮的《Design Patterns》奉为编程圣经,而我却觉得大可不必。设计模式说白了,就是在特定环境下解决某类常见问题的一种套路,按着这种套路去做就会很得心应手。但是,这并不代表这些设计模式是放之四海皆准,更不是说套着设计模式做的东西就一定是最好维护、性能最佳、至高无上的了。就算你不知道什么设计模式,只要思路正确,也能写出设计模式来。

 

有大师说,设计模式其实是对语言缺陷的一种弥补。这里有两层意思,一是说设计模式是基于语言的,因为语言存在缺陷,能力有所不足,所以需要设计模式去提供一种通用的解决方式去弥补这些不足;二是说,如果脱离具体语言及具体环境,生搬硬套设计模式甚至会是一种束缚,设计模式并非绝对正确。

了解了上面的观点之后,下面就通过js的单例模式来看看,JavaScript的设计模式是怎么回事。

 

单例模式(Singleton)

传统的单例模式大概是这么回事(Java版):

Js代码   收藏代码
  1. public class Singleton{  
  2.     private static Singleton instance;  
  3.     private int property;//某些属性  
  4.     private Singleton(){  
  5.         //通过私有的构造函数来保证只有一个实例  
  6.     }  
  7.     public static synchronized Singleton getInstance(){  
  8.         if (instance == null) {  
  9.             instance = new Singleton();  
  10.         }  
  11.         return instance;  
  12.     }  
  13.     public void method(){//某些方法}  
  14. }  
  传统单例模式的特点就是利用私有构造函数来保证只有一个实例,使不同地方调用的都是同一个实例对象。然而这一点对于js来说就有点弱爆了,因为js是允许全局变量的,解决单例的办法很简单,定义一个全局对象就完事了(是不是觉得很原始,完全没什么模式可言):
Js代码   收藏代码
  1. var jsSingleton = {  
  2.     property:"something",  
  3.     method:function(){  
  4.         console.log('hello world');  
  5.     }  
  6. }  

 调用也很简单,无需顾及什么类名,任何地方访问jsSingleton都是同一个对象,完全不用担心。如果你气急败坏的跟我说,你这只是饿汉式的,懒汉式单例呢?如果真的创建对象时很耗费资源,或者是你对性能洁癖到一定程度,那也只能让js东施效颦的学一下传统的方式了:

Js代码   收藏代码
  1. var jsLazySingleton =(function(){  
  2.     var instance;  
  3.     //利用闭包来解决私有构造函数的问题(后面有解释)  
  4.     function init (){  
  5.         return {  
  6.             property: 'some thing',  
  7.             method: function(){}  
  8.         }  
  9.     }  
  10.     return {  
  11.         getInstance :function(){  
  12.             //如果instance存在则返回,不存在则调用init()  
  13.             return instance || instance = init();  
  14.         }  
  15.     }  
  16. })();  
   

模块模式(Module)

在js的懒汉式单例模式中,其实也用到了另一种设计模式,即模块模式。在传统软件工程中,模块模式被定义为给类提供私有和公共封装的一种方法,也就是我们常说的“模块化”。在Java里面Class就是一种模块,解决了属性、方法的封装问题,它的模块模式直接就融合到语言特性里面了,因此也没什么模块模式的说法;然而对于JavaScript这种过于灵活的语言,这种最基本的私有化封装却需要用一个设计模式来解决了。

 

在JavaScript中,“private”是作为保留字,而不是关键字的,也就是说,JavaScript没有私有化这一功能(纠结吧)。解决解决这问题有两种方法,一是定义变量的时候在前面加上下划线“_”,也就是告诉其他开发者,不要动这个变量哟;另一种则是利用闭包。第一种方法并不是真正的私有,只是一种规范,如果要做到真正的私有,还是要用第二种方法——闭包。

我们创建一个匿名函数,然后立即运行它,此匿名函数中的所有代码都存在于一个“闭包”之中,从而得到私有性,并在特定作用域中保持可被访问。方法如下:

Js代码   收藏代码
  1. (function () {  
  2.     //此作用域的所有变量、函数依旧可在特定作用域中被访问  
  3. })();  
  先用括号把函数定义括起来,从而得到该函数对象,然后后面的括号是立即运行它。这种形式可以在很多js库中见到,例如jQuery:
Js代码   收藏代码
  1. (function( window, undefined ) {  
  2.     ......  
  3.     //最后一行  
  4.     window.jQuery = window.$ = jQuery;  
  5. })(window);  
  我们看到jQuery把window这个全局变量传进匿名函数中,然后把内部定义的jQuery赋值给了window,从而在全局作用域中都可以通过“$”符号来访问匿名函数中的内容(想了解更多关于闭包的内容,可以看看《 jQuery自运行匿名函数 》《 闭包与作用域链 》)。

弄清楚这些之后,我们就来看看js模块模式的基本样子吧:

Js代码   收藏代码
  1. var Module = (function () {  
  2.     var my={},  
  3.         privateVar = 8;//私有属性  
  4.     function privateFun() {//私有方法  
  5.         return ++privateVar;  
  6.     };  
  7.     my.publicVar = 1;//公共属性  
  8.     my.moduleFun = function () {//公共方法  
  9.         return privateFun();  
  10.     };  
  11.     return my;  
  12. }());  
  13. console.log(Module.publicVar);//1  
  14. console.log(Module.publicFun());//9  
  在匿名函数中我们返回了一个my变量给Module作为外部访问闭包内容的接口,除闭包内my之外的内容都得到了私有性保护,闭包的数据在Module变量的作用域中保持可以访问。

好了,模块模式解决了JavaScript私有化的问题,我们可以利用它来定义命名空间、单例、拥有私有化封装的对象等等。然而模块模式也并非尽善尽美。例如,我们定义私有、公共变量的方法是不同的,当开发过程中我们需要改变某个变量的可见性的时候,就不得不在它所有出现过的地方进行修改;并且JavaScript作为动态编译的语言,我们可以随时给对象添加属性、方法,然而我们在闭包之外定义的方法是无法直接访问私有数据的

 

更高级的模块模式

虽然前面提到的模块模式对于大多数开发者来说已经足够了,但是模块模式还可以被改进的更加强大、更加易于扩展。

扩展性

之前所说的模板模式有一个限制,就是整个模板必须定义在一个文件中。曾面对一大堆代码工作的人肯定明白将它划分为多个文件的意义。好在,有个巧妙的方法来扩展我们的Modules。首先,在匿名函数的参数中导入Module,然后给它添加属性,然后再导出它。下面这个例子是在另一个文件中对上面提到的Module进行扩展(必须是全局作用域的情况):

Js代码   收藏代码
  1. var Module = (function (my) {  
  2.     my.anotherFun = function () {  
  3.         // do someting...  
  4.     };  
  5.     return my;  
  6. }(Module||{}));//注意,这里还有个“{}”  
  我们重新用var关键字定义Module以保证一致性,并且如果之前Module没有被创建的话,在这里会自动的被创建为空对象“{}”。当这段代码运行结束之后,我们的Module又有了一个新的公共方法Module.anotherFun()。每个此扩展的Module都有自己独有的私有属性、方法。由于导入的时候可以自动创建,这些包含Module定义的文件之间可以以任意顺序加载(如果存在依赖关系,必须按次序加载的话,那你按次序就好了)。

跨文件私有属性共享

目前的多文件扩展Module还有个一严重的限制,那就是每个文件的Module都只保持自己的私有状态,无法访问其他文件的私有属性。当然,这也可以被解决。下面是解决多文件私有属性共享的一个方法:

Js代码   收藏代码
  1. var MODULE = (function () {  
  2.     //将所有的私有属性、方法都定义在_private对象中  
  3.     //每个扩展Module都可以通过my._private来访问  
  4.     var my = {},  
  5.     _private = my._private = {},  
  6.    
  7.     _seal = function (){  
  8.         //密封,删除所有私有数据的可访问性  
  9.         delete my._private;  
  10.     },  
  11.     _unseal = function (){  
  12.         //解封,让私有数据重新可访问  
  13.         my._private = _private;  
  14.     };  
  15.    
  16.     my.extend = function(otherModules){  
  17.         //必须通过此方法来添加扩展Module文件  
  18.         _unseal();  
  19.         //add other modules  
  20.         _seal();//异步调用,此处只是示意,真正的代码并非如此  
  21.     }  
  22.     return my;  
  23. }());  

 上面的Module文件必须第一个被加载,然后利用Module.extend来加载其他的扩展。my.extend实际上是通过类似Labjs的工具来并行加载其他的Module。每个扩展Module都通过my._private来传递私有性,并且都需要将传来的my._private保存为自己的私有属性,所有对my._private的修改都将反应到其他扩展Module中去。此方法参考于Ben Cherry的《JavaScript Module Pattern: In-Depth》。

 

小结一下

看完了JavaScript的单例模式和模块模式,是不是有点感叹与JavaScript设计模式的不伦不类呢?这些就是语言特性的体现,设计模式是依赖于语言特性的,是对语言能力的一种补充。JavaScript的设计模式并非不伦不类,很多JavaScript也有很多优雅的设计模式,可以让传统设计模式相形见绌的。

接下来还打算写几篇关于JavaScript设计模式的文章,敬请期待。



在javascript中,单例(singleton)就是指只有一个实例的对象,而不需要用new关键字去创建一个对象示例。单例其实有点类似于C# /C++里面的静态类。在javascript中,是以对象字面量的方式来创建单例对象的,也就是以花括号包裹起来的键值对的JSON形式来创建的。例如:

 

  var  singletonObj = {
    name:
' John ' ,
    method:
function (){
        
// 方法代码
 }
};
 

 模块模式则是用来为单例创建私有变量和特权方法(公有方法),从而能增强单例的可访问性。以模块模式定义的私有变量和私有函数只有单例对象本身的特权(公有)方法可以访问到,其他外部的任何对象都不可以。其语法形式如下:

复制代码
var  singleton  =   function (){
    
// 私有变量
     var  privateVariable  =   10 ;
    
    
// 私有函数
     function  privateFunction(){
        
return   false ;
    }

    
// 返回对象
     return  {
        
// 公有属性
        publicProperty:  true ,
        
        
// 公有属性和公有方法
        publicMethod :  function (){
            privateVariable
++ ;
            
return  privateFunction();
        }
    };
}();
复制代码

 

 上面的代码中,我们创建了一个匿名函数并赋值给变量singleton,然后立即调用这个函数返回可以公开的公有方法和属性的对象实例。在匿名函数内部,首先定义了私有变量和私有函数,然后将一个对象实例作为函数值返回,这样返回的对象中只包含可以公开的属性和方法。因为这个返回的对象在匿名函数内部定义,因此它内部的公有方法和属性都可以访问私有变量和函数。这种模式适合需要对单例进行一些初始化,又需要维护私有变量时。

 单例对象中公有属性和公有方法的调用与一般对象的属性和方法调用一样,使用点表示法或者方括号语法来调用单例对象的公有成员。例如:

alert(singleton.publicProperty);

alert(singleton[publicProperty]);  

 执行结果都是:true 


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值