javascript设计模式(二) 代理模式 观察者模式

代理模式是为一个对象提供一个代用品或占位符,以便控制对他的访问。在实际开发过程中代理模式也是很有意义的,我们会遇到这样的情况,当客户不方便直接访问或者是直接访问不能满足需求甚至说不能直接访问时,我们可以提供一个中间的替身对象来更好的进行对实体对象的访问,客户并不关心他访问的是替身还是实体,只要能完成需求就好。
代理模式的种类有许多,在js里面比较常用的是虚拟代理和缓存代理,这里我们着重介绍一下这两种。
虚拟代理是指把一些开销很大的对象,延迟到他真正需要的时候才去创建,这跟函数分时的概念有些类似,下面看一个经常遇见的例子图片预加载

var myImage = (function(){ var imgNode = document.createElement( "img" ); document.body.appendChild( imgNode ); return function( src ){ imgNode.src = https://www.it610.com/article/src; }; })(); var proxyImage = (function(){ var img = new Image; img.onload = function(){ myImage( this.src ); }; return function( src ){ myImage('onloading.gif' ); img.src = https://www.it610.com/article/src; }; })(); proxyImage('https://xxxxx' );


myImage负责添加img标签,而代理proxyImage负责预加载图片,并在预加载后将请求交给本体


myImage 首先创建了一个img标签并将其添加到body上,然后返回一个函数,函数功能为设置myImage内闭包的imgNode的地址。proxyImage 首先new了一个Image对象,然后对其设置一个onload事件,功能为调用myImage函数实现当图片加载完后设置图片地址, 最后返回了一个函数,功能是先设置loading图片在给闭包内的img对象设置src属性,以便onload事件触发后读取地址。
代理负责预加载,而且并不关心本体如何工作,他的职责只有预加载,而本体则只负责添加并设置img地址,同时留给客户的接口也只有一个输入图片地址的函数,代理完全隐藏起来。这也是代理存在的意义,代理起到了一个转接装饰的作用,最终请求还是被转送给了本体,二者基本职责分离,符合封闭-开放原则。
缓存代理是为一些开销大的运算结果提供暂时的存储,如果有重复调用的情况,则可以直接从缓存中读取之前的运算结果,下面看一下简单的计算存储例子
var mult = function(){//计算传入参数的乘积 var a = 1, i = 0; for( ; arguments[ i ]; i++){ a = a * arguments[ i ]; } return a; }; var proxyMult = (function(){ var cache = {}; //缓存存储对象return function(){ var args = Array.prototype.join.call( arguments, ','); if( args in cache ){ return cache[ args ]; } return (cache[ args ] = mult.apply( this, arguments )); }; })();

总体来看,mult只负责计算结果,客户也只要求计算结果,中间加入一个代理根本原因还是为了更好的实现mult的功能。



观察者模式又称发布-订阅模式,定义了对象之间的一种一对多的依赖关系,当被依赖对象的状态发生改变时,所有依赖于它的对象都将得到通知。这种模式可以广泛地应用到异步编程中,可以替代传递回调函数,想要在任意事件发生后调用某函数,只需订阅这个事件,同时运用此模式后,发布者和订阅者之间是一个松耦合却又有一定联系的状态,他们不必知道彼此各自的实现细节,只要双方功能完备即可通过订阅关系进行联系,当有新订阅者订阅时,发布者不需要任何变动;当有发布者需要改变时,订阅者也不会受到影响。
其实只要我们在dom上绑定过事件函数或者用过ajax甚至回调函数,那么恭喜你,你已经尝试过了发布订阅模式
document.body.addEventListener( 'click', function(){ alert(2); }, false ); document.body.click();


这里是订阅了document.body上的click事件,当body节点被点击后则发布点击消息,然后订阅者就接收到了消息。下面看一个比较完整的js例子,售楼处和消费者



var event = { cilentList:{}, listen: function( key, fn ){ if( !this.clientList[ key ] ){//如果之前没有订阅者订阅这个信息,则新创建一个发布要求数组 this.clientList[ key ] = []; } this.clientList[ key ].push( fn ); }, trigger: function(){ var key = Array.prototype.shift.call( arguments ), fns = this.clientList[ key ]; if( !fns || fns.length === 0 ){//如果此消息没有被人订阅 return false; }for( vari = 0, fn; fn = fns[ i++ ]; ){ fn.apply( this, arguments ); } } }; var installEvent = function( obj ){ for( vari in event ){ obj[ i ] = event[ i ]; } }; var salesOffices = {}; installEvent( salesOffices ); salesOffices.listen( 'squareMeter88', function( price ){ console.log( '价格=' + price ); }); salesOffices.listen( 'squareMeter100', function( price ){ console.log( '价格=' + price ); }); salesOffices.trigger( 'squareMeter88', 2000000 ); salesOffices.trigger( 'squareMeter100', 3000000 );



event对象包含着一个售楼处对象应该有的属性,订阅列表,监听函数,发布函数,订阅列表里面有订阅者想要的信息以及对应订阅者的发布要求(函数),订阅信息是键值,各个订阅者的要求是函数数组,监听函数是监听订阅者需求并储存在订阅列表里,发布函数里面则是发布时的具体实现,主要是遍历订阅列表。
【javascript设计模式(二) 代理模式 观察者模式】发布订阅模式优点十分明显,时间解耦和对象解耦,且应用十分广泛,但还是存在一些问题,本身要解耦势必多创建一些不必要的对象,很可能就会有你订阅了一个事件,但他最后也没有发生,而订阅者却会一直存在于内存中。

    推荐阅读