首页 > 代码库 > 深入理解javascript之设计模式
深入理解javascript之设计模式
设计模式
设计模式是命名、抽象和识别对可重用的面向对象设计实用的的通用设计结构。
设计模式确定类和他们的实体、他们的角色和协作、还有他们的责任分配。
每个设计模式都聚焦于一个面向对象的设计难题或问题。
它描写叙述了在其他设计的约束下它是否能使用。使用它后的后果和得失。
由于我们必须终于实现我们的设计模式,所以每个设计模式都提供了样例,代码来对实现进行阐释.
尽管设计模式被描写叙述为面向对象的设计,它们基于那些已经被主流面向对象语言实现过的解决方式...”。
种类
设计模式能够被分成几个不同的种类。
在这个部分我们将分为三类:创建型设计模式、结构设计模式、行为设计模式。
创建型设计模式
创建型设计模式关注于对象创建的机制方法,通过该方法,对象以适应工作环境的方式被创建。主要的对象创建方法可能会给项目添加额外的复杂性,而这些模式的目的就是为了通过控制创建过程解决问题。
属于这一类的一些模式是:构造器模式(Constructor),工厂模式(Factory),抽象工厂模式(Abstract),原型模式(Prototype),单例模式(Singleton)以及 建造者模式(Builder)。
结构设计模式
结构模式关注于对象组成和通常识别的方式实现不同对象之间的关系。该模式有助于在系统的某一部分发生改变的时候,整个系统结构不须要改变。该模式相同有助于对系统中某部分没有达到某一目的的部分进行重组。
在该分类下的模式有:装饰模式,外观模式,享元模式。适配器模式和代理模式。
行为设计模式
行为模式关注改善或精简在系统中不同对象间通信。
行为模式包含:迭代模式。中介者模式,观察者模式和訪问者模式。
以下我们通过分开介绍各个经常使用的设计模式,来加深对设计模式的理解。
构造器模式
构造器是一个当新建对象的内存被分配后,用来初始化该对象的一个特殊函数。对象构造器是被用来创建特殊类型的对象的,首先它要准备使用的对象。其次在对象初次被创建时,通过接收參数。构造器要用来对成员的属性和方法进行赋值。
因为javascript不支持类的概念,所以必须通过构造器来使用newkeyword初始化对象。
一个基础的构造器代码例如以下:
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() );
工厂模式
一个工厂能提供一个创建对象的公共接口。我们能够在当中指定我们希望被创建的工厂对象的类型。说的简单点。就像饮水机,要咖啡还是牛奶取决于你按哪个button。
简单工厂模式在创建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=http://www.mamicode.com/responseText;"fail"); } });
ajax实际上就是一个工厂方法,至于究竟是用get方法还是post方法,都由后面的代码来决定。这就是前面所说的“一个工厂能提供一个创建对象的公共接口,我们能够在当中指定我们希望被创建的工厂对象的类型”。
单例模式
单例模式之所以这么叫。是由于它限制一个类仅仅能有一个实例化对象。经典的实现方式是。创建一个类,这个类包括一个方法,这种方法在没有对象存在的情况下,将会创建一个新的实例对象。
假设对象存在,这种方法仅仅是返回这个对象的引用。可是javascript本来就是无类的,所以简单地来说。就是没有就创建,有就不创建直接用。
那么我们看看现实中的案例吧。点击一个button后出现一个遮罩层,这是一个经常使用的需求吧。代码例如以下:
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 ) } )
外观模式
外观模式是一种无处不在的模式,外观模式提供一个高层接口,这个接口使得client或者子系统调用起来更加方法。
比方:
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就能够写成享元。
原理事实上非常easy, 把刚隐藏起来的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里的$实现得更酷, 但你又不想让你的project师去学习新的库和语法. 那一个适配器就能让你完毕这件事情.
$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系列也将告一段落。
深入理解javascript之设计模式