深入理解javascript之设计模式

设计模式

设计模式是命名、抽象和识别对可重用的面向对象设计有用的的通用设计结构。设计模式确定类和他们的实体、他们的角色和协作、还有他们的责任分配。

每一个设计模式都聚焦于一个面向对象的设计难题或问题。它描述了在其它设计的约束下它能否使用,使用它后的后果和得失。因为我们必须最终实现我们的设计模式,所以每个设计模式都提供了例子,代码来对实现进行阐释.

虽然设计模式被描述为面向对象的设计,它们基于那些已经被主流面向对象语言实现过的解决方案...”。

种类

设计模式可以被分成几个不同的种类。在这个部分我们将分为三类:创建型设计模式、结构设计模式、行为设计模式。

创建型设计模式

创建型设计模式关注于对象创建的机制方法,通过该方法,对象以适应工作环境的方式被创建。基本的对象创建方法可能会给项目增加额外的复杂性,而这些模式的目的就是为了通过控制创建过程解决这个问题。

属于这一类的一些模式是:构造器模式(Constructor),工厂模式(Factory),抽象工厂模式(Abstract),原型模式(Prototype),单例模式(Singleton)以及 建造者模式(Builder)。

结构设计模式

结构模式关注于对象组成和通常识别的方式实现不同对象之间的关系。该模式有助于在系统的某一部分发生改变的时候,整个系统结构不需要改变。该模式同样有助于对系统中某部分没有达到某一目的的部分进行重组。

在该分类下的模式有:装饰模式,外观模式,享元模式,适配器模式和代理模式。

行为设计模式

行为模式关注改善或精简在系统中不同对象间通信。

行为模式包括:迭代模式,中介者模式,观察者模式和访问者模式。

下面我们通过分开介绍各个常用的设计模式,来加深对设计模式的理解。

构造器模式

构造器是一个当新建对象的内存被分配后,用来初始化该对象的一个特殊函数。对象构造器是被用来创建特殊类型的对象的,首先它要准备使用的对象,其次在对象初次被创建时,通过接收参数,构造器要用来对成员的属性和方法进行赋值。

由于javascript不支持类的概念,所以必须通过构造器来使用new关键字初始化对象。一个基础的构造器代码如下:

function Car( model, year, miles ) {

  this.model = model;
  this.year = year;
  this.miles = miles;

  this.toString = function () {
    return this.model + " has done " + this.miles + " miles";
  };
}

// 使用:

// 我们可以示例化一个Car
var civic = new Car( "Honda Civic", 2009, 20000 );
var mondeo = new Car( "Ford Mondeo", 2010, 5000 );

// 打开浏览器控制台查看这些对象toString()方法的输出值
// output of the toString() method being called on
// these objects
console.log( civic.toString() );
console.log( mondeo.toString() );

但是这样的话,继承起来比较麻烦,而且每个Car构造函数创建的对象中,toString之类的函数都会被重新定义。所以还是要利用原型,来实现最佳的构造器:

function Car( model, year, miles ) {

  this.model = model;
  this.year = year;
  this.miles = miles;

}

// 注意这里我们使用Note here that we are using Object.prototype.newMethod 而不是
// Object.prototype ,以避免我们重新定义原型对象
Car.prototype.toString = function () {
  return this.model + " has done " + this.miles + " miles";
};

// 使用:

var civic = new Car( "Honda Civic", 2009, 20000 );
var mondeo = new Car( "Ford Mondeo", 2010, 5000 );

console.log( civic.toString() );
console.log( mondeo.toString() );

工厂模式

一个工厂能提供一个创建对象的公共接口,我们可以在其中指定我们希望被创建的工厂对象的类型。说的简单点,就像饮水机,要咖啡还是牛奶取决于你按哪个按钮。

简单工厂模式在创建ajax对象的时候可以体现出来,可以通过jquery中的$.ajax方法来理解,也可以通过我自己写的一个ajax方法来理解,地址在:http://runjs.cn/code/j5dkikwu

	ajax("test002.txt",{
		type:"GET",
		data:{
			name:"liuf",
			age:23
		},
		onsuccess:function(responseText,xhr){
			document.getElementById("input").value=responseText;
		},
		onfail:function(){
			//document.write("fail");
		}
	});

ajax实际上就是一个工厂方法,至于到底是用get方法还是post方法,都由后面的代码来决定。这就是前面所说的“一个工厂能提供一个创建对象的公共接口,我们可以在其中指定我们希望被创建的工厂对象的类型”。

单例模式

单例模式之所以这么叫,是因为它限制一个类只能有一个实例化对象。经典的实现方式是,创建一个类,这个类包含一个方法,这个方法在没有对象存在的情况下,将会创建一个新的实例对象。如果对象存在,这个方法只是返回这个对象的引用。但是javascript本来就是无类的,所以简单地来说,就是没有就创建,有就不创建直接用。

那么我们看看现实中的案例吧,点击一个按钮后出现一个遮罩层,这是一个常用的需求吧。代码如下:

var createMask = function(){

   return document,body.appendChild(  document.createElement(div)  );

}
$( 'button' ).click( function(){

   var mask  = createMask();

   mask.show();

})

这样写就会出现一个问题,就是每次调用createMask都会创建一个新的div,虽然可以在隐藏遮罩层时将其remove,但是这样还是会带来性能的损耗,那么可以做如下改进,就是在页面一开始就创建div,代码如下:

var mask = document.body.appendChild( document.createElement( ''div' ) );

$( ''button' ).click( function(){

   mask.show();

} )

这样确实可以保证页面只会创建一个遮罩层,但是也有一个问题,就是如果用户不需要用到这个div,岂不是白白创建了它。于是我们可以借助一个变量来判断是否已经创建过div,代码如下:

var mask;

var createMask = function(){

if ( mask ) return mask;

else{

mask = document,body.appendChild(  document.createElement(div)  );

return mask;

}

}

这样看起来不错,但是mask作为一个全局变量,是否会造成污染呢?所以最好的办法如下:

var createMask = function(){
  var mask;
  return function(){
       return mask || ( mask = document.body.appendChild( document.createElement('div') ) )
  }
}()

这就是前面所说的“没有就创建,有就不创建直接用”。没错,单例模式就是这么简单,设计模式其实并不难,编程中我们其实一直有用到,只是自己没有发现罢了。

桥接模式

桥接模式就是将实现部分和抽象部分分离开来,以便两者可以独立的变化。在实现api的时候,桥接模式非常常用。

我们以javascript的forEach方法为例:

forEach = function( ary, fn ){
  for ( var i = 0, l = ary.length; i < l; i++ ){
    var c = ary[ i ];
    if ( fn.call( c, i, c ) === false ){
      return false;
    }
   }
}

可以看到,forEach函数并不关心fn里面的具体实现. fn里面的逻辑也不会被forEach函数的改写影响.

使用代码如下:

forEach( [1,2,3], function( i, n ){

 alert ( n*2 )

} )

forEach( [1,2,3], function( i, n ){

  alert ( n*3 )

} )

外观模式

外观模式是一种无处不在的模式,外观模式提供一个高层接口,这个接口使得客户端或者子系统调用起来更加方法。比如:

var getName = function(){
  return ''svenzeng"
}
var getSex = function(){
   return 'man'
}

现在我要调用两个方法,我就可以使用一个更加高层的接口来调用:

var getUserInfo = function(){
  var info = getName () + getSex ();
  return info;
}

这样就方便组装,如果一开始就把两个写到一个函数中,那就不能够只单独调用其中一个了。

享元模式

享元模式是一个优化重复、缓慢和低效数据共享代码的经典结构化解决方案。它的目标是以相关对象尽可能多的共享数据,来减少应用程序中内存的使用(例如:应用程序的配置、状态等)。通俗的讲,享元模式就是用来减少程序所需的对象个数。

举一个例子,网页中的瀑布流,或者webqq的好友列表中,每次往下拉时,都会创建新的div。那么如果有很对div呢?浏览器岂不是卡死了?所以我们会想到一种办法,就是把已经消失在视线外的div都删除掉,这样页面就可以保持一定数量的节点,但是频繁的删除和添加节点,又会带来很大的性能开销。

这个时候就可以用到享元模式了,享元模式可以提供一些共享的对象以便重复利用。比如页面中只能显示10个div,那始终出现在用户视线中的这10个div就可以写成享元。

原理其实很简单, 把刚隐藏起来的div放到一个数组中, 当需要div的时候, 先从该数组中取, 如果数组中已经没有了, 再重新创建一个. 这个数组里的div就是享元, 它们每一个都可以当作任何用户信息的载体.代码如下:

var getDiv = (function(){
    var created = [];
    var create = function(){
          return document.body.appendChild( document.createElement( 'div' ) );
    }
    var get = function(){
         if ( created.length ){
              return created.shift();
          }else{
                return create();
           }
     }
/* 一个假设的事件,用来监听刚消失在视线外的div,实际上可以通过监听滚动条位置来实现 */
      userInfoContainer.disappear(function( div ){
              created.push( div );
        })
 })()
  var div = getDiv();
  div.innerHTML = "${userinfo}";

适配器模式

适配器模式就是将一个类的接口转换成客户希望的另外一个接口。通俗一点的说,将像苹果手机不能差在电脑机箱上,必须有一个转换器,而这个转换器就是适配器。

在程序里适配器模式也经常用来适配2个接口, 比如你现在正在用一个自定义的js库. 里面有个根据id获取节点的方法$id(). 有天你觉得jquery里的$实现得更酷, 但你又不想让你的工程师去学习新的库和语法. 那一个适配器就能让你完成这件事情.

$id = function( id ){

  return jQuery( '#' + id )[0];

}

这样就不用再一个一个的修改了。

代理模式

代理模式就是把对一个对象的访问,交给另一个代理对象来操作。说得通俗一点,程序员每天写日报,日报最后会给总监审阅,但是如果所有人都直接发给总监,那总监就没法工作了。所以每个人会把自己的日报发给自己的组长,再由组长转发给总监。这个组长就是代理。

编程中用到代理模式的情况也不少,比如大量操作dom时,我们会先创建文档碎片,再统一加到dom树中。

示例如下:

中介者模式

中介者模式是观察者模式中的共享被观察者对象。在这个系统中的对象之间直接的发布/订阅关系被牺牲掉了,取而代之的是维护一个通信的中心节点。中介者模式和代理模式是有区别的,区别如下:

中介者对象可以让各个对象之间不需要相互引用,从而使其耦合松散,而且可以独立的改变透明之间的交互。通俗点讲,银行在存款人和贷款人之间也能看成一个中介。存款人A并不关心他的钱最后被谁借走。贷款人B也不关心他借来的钱来自谁的存款。因为有中介的存在,这场交易才变得如此方便。

在编程中,大名鼎鼎的MVC结构中的Controller不就是一个中介者吗?拿backbone举例. 一个mode里的数据并不确定最后被哪些view使用. view需要的数据也可以来自任意一个mode. 所有的绑定关系都是在controler里决定. 中介者把复杂的多对多关系, 变成了2个相对简单的1对多关系。

示例代码如下:

var mode1 = Mode.create(),  mode2 = Mode.create();
var view1 = View.create(),   view2 = View.create();
var controler1 = Controler.create( mode1, view1, function(){
  view1.el.find( ''div' ).bind( ''click', function(){
    this.innerHTML = mode1.find( 'data' );
  } )
})
var controler2 = Controler.create( mode2 view2, function(){
  view1.el.find( ''div' ).bind( ''click', function(){
    this.innerHTML = mode2.find( 'data' );
  } )
})

观察者模式

观察者模式是这样一种设计模式。一个被称作被观察者的对象,维护一组被称为观察者的对象,这些对象依赖于被观察者,被观察者自动将自身的状态的任何变化通知给它们。

当一个被观察者需要将一些变化通知给观察者的时候,它将采用广播的方式,这条广播可能包含特定于这条通知的一些数据。

当特定的观察者不再需要接受来自于它所注册的被观察者的通知的时候,被观察者可以将其从所维护的组中删除。

通俗点理解,就是面试官是被观察者,而等待通知的人是观察者。

javascript中平时接触的dom事件,其实就是一种观察者模式的体现:

div.onclick  =  function click (){

   alert ( ''click' )

}

只要订阅了div的click事件,当点击div的时候,click函数就会执行。

观察者模式可以很好的实现连个模块之间的解耦,假如一个多人合作的项目,我负责Map和Gamer模式:

loadImage(  imgAry,  function(){

Map.init();

Gamer.init();

} )

<p>而别人负责loadImage方法的维护,如果有一天,我要加上一个Sound模块,而我无权动用别人的代码,只能空空等待别人回来添加:</p><pre name="code" class="html">loadImage(  imgAry,  function(){

Map.init();

Gamer.init();

Sount.init();

} )

所以我们可以做如下改动:

loadImage.listen( ''ready', function(){

    Map.init();

})

loadImage.listen( ''ready', function(){

   Gamer.init();

})

loadImage.listen( ''ready', function(){

   Sount.init();

})

loadImage完成之后,不用再关心未来会发送什么,接下来它只需要发送一个信号:

loadImage.trigger( ‘ready’ );

所有监听ready事件的对象就都会收到通知。这就是观察者模式的应用场景。

说到这里,常用的设计模式也讲完了,深入理解javascript系列也将告一段落。

版权声明:本文为博主原创文章,未经博主允许不得转载。

时间: 2024-10-13 16:28:59

深入理解javascript之设计模式的相关文章

深入理解javascript系列之序章

从今天开始,博主会对javascript进行深入理解分析并将自己学习到的一些知识分析整理成博文,来让大家一起共同学习和讨论.如果有什么疑问或者建议,可以和博主联系.如果包含什么错误,希望大家可以进行讨论和指导. 该系列是建立在已经基本掌握了javascript中的基础语法,进入更深一步学习的进阶笔记. 该系列目录分为: 深入理解javascript之内存分配 深入理解javascript之null和undefined 深入理解javascript之作用域 深入理解javascript之原型 深入

js架构设计模式——理解javascript中的MVVM开发模式

理解javascript中的MVVM开发模式 http://blog.csdn.net/slalx/article/details/7856769 MVVM的全称是Model View ViewModel,这种架构模式最初是由微软的MartinFowler作为微软软件的展现层设计模式的规范提出,它是MVC模式的衍生物,MVVM模式的关注点在能够支持事件驱动的UI开发平台,例如HTML5,[2][3] WindowsPresentation Foundation (WPF), Silverligh

深入理解JavaScript系列(25):设计模式之单例模式

介绍 从本章开始,我们会逐步介绍在JavaScript里使用的各种设计模式实现,在这里我不会过多地介绍模式本身的理论,而只会关注实现.OK,正式开始. 在传统开发工程师眼里,单例就是保证一个类只有一个实例,实现的方法一般是先判断实例存在与否,如果存在直接返回,如果不存在就创建了再返回,这就确保了一个类只有一个实例对象.在JavaScript里,单例作为一个命名空间提供者,从全局命名空间里提供一个唯一的访问点来访问该对象. 正文 在JavaScript里,实现单例的方式有很多种,其中最简单的一个方

深入理解JavaScript系列(31):设计模式之代理模式

介绍 代理,顾名思义就是帮助别人做事,GoF对代理模式的定义如下: 代理模式(Proxy),为其他对象提供一种代理以控制对这个对象的访问. 代理模式使得代理对象控制具体对象的引用.代理几乎可以是任何对象:文件,资源,内存中的对象,或者是一些难以复制的东西. 正文 我们来举一个简单的例子,假如dudu要送酸奶小妹玫瑰花,却不知道她的联系方式或者不好意思,想委托大叔去送这些玫瑰,那大叔就是个代理(其实挺好的,可以扣几朵给媳妇),那我们如何来做呢? // 先声明美女对象 var girl = func

深入理解JavaScript系列(36):设计模式之中介者模式

介绍 中介者模式(Mediator),用一个中介对象来封装一系列的对象交互.中介者使各对象不需要显式地相互引用,从而使其耦合松散,而且可以独立地改变它们之间的交互. 主要内容来自:http://www.addyosmani.com/resources/essentialjsdesignpatterns/book/#mediatorpatternjavascript 正文 软件开发中,中介者是一个行为设计模式,通过提供一个统一的接口让系统的不同部分进行通信.一般,如果系统有很多子模块需要直接沟通,

深入理解JavaScript系列(30):设计模式之外观模式

介绍 外观模式(Facade)为子系统中的一组接口提供了一个一致的界面,此模块定义了一个高层接口,这个接口值得这一子系统更加容易使用. 正文 外观模式不仅简化类中的接口,而且对接口与调用者也进行了解耦.外观模式经常被认为开发者必备,它可以将一些复杂操作封装起来,并创建一个简单的接口用于调用. 外观模式经常被用于JavaScript类库里,通过它封装一些接口用于兼容多浏览器,外观模式可以让我们间接调用子系统,从而避免因直接访问子系统而产生不必要的错误. 外观模式的优势是易于使用,而且本身也比较轻量

深入理解JavaScript系列(38):设计模式之职责链模式

介绍 职责链模式(Chain of responsibility)是使多个对象都有机会处理请求,从而避免请求的发送者和接受者之间的耦合关系.将这个对象连成一条链,并沿着这条链传递该请求,直到有一个对象处理他为止. 也就是说,请求以后,从第一个对象开始,链中收到请求的对象要么亲自处理它,要么转发给链中的下一个候选者.提交请求的对象并不明确知道哪一个对象将会处理它——也就是该请求有一个隐式的接受者(implicit receiver).根据运行时刻,任一候选者都可以响应相应的请求,候选者的数目是任意

深入理解JavaScript系列(33):设计模式之策略模式(转)

介绍 策略模式定义了算法家族,分别封装起来,让他们之间可以互相替换,此模式让算法的变化不会影响到使用算法的客户. 正文 在理解策略模式之前,我们先来一个例子,一般情况下,如果我们要做数据合法性验证,很多时候都是按照swith语句来判断,但是这就带来几个问题,首先如果增加需求的话,我们还要再次修改这段代码以增加逻辑,而且在进行单元测试的时候也会越来越复杂,代码如下: validator = { validate: function (value, type) { switch (type) { c

深入理解JavaScript系列(43):设计模式之状态模式

介绍 状态模式(State)允许一个对象在其内部状态改变的时候改变它的行为,对象看起来似乎修改了它的类. 正文 举个例子,就比如我们平时在下载东西,通常就会有好几个状态,比如准备状态(ReadyState).下载状态(DownloadingState).暂停状态(DownloadPausedState).下载完毕状态(DownloadedState).失败状态(DownloadFailedState),也就是说在每个状态都只可以做当前状态才可以做的事情,而不能做其它状态能做的事儿. 由于Stat