javascript设计模式与开发实践阅读笔记(6)——代理模式

代理模式:是为一个对象提供一个代用品或占位符,以便控制对它的访问。

代理模式的关键是,当客户不方便直接访问一个对象或者不满足需要的时候,提供一个替身对象来控制对这个对象的访问,客户实际上访问的是替身对象。替身对象对请求做出一些处理之后,再把请求转交给本体对象。
基本可以理解为粉丝(客户),经纪人(代理),偶像(对象)。经纪人就相当于偶像的代理,需求直接提给经纪人,经纪人这边可以进行很多逻辑上的处理,比如可以帮助偶像过滤掉很多请求等等。

1.保护代理和虚拟代理

像上面那种,请求被代理拒绝掉就是保护代理。
把一些开销很大的对象,延迟到真正需要它的时候才去创建的代理,就是虚拟代理。
保护代理用于控制不同权限的对象对目标对象的访问,但在JavaScript并不容易实现保护代理,因为我们无法判断谁访问了某个对象。而虚拟代理是最常用的一种代理模式。

2.虚拟代理实现图片预加载

在Web 开发中,图片预加载是一种常用的技术,如果直接给某个img标签节点设置src属性,由于图片过大或者网络不佳,图片的位置往往有段时间会是一片空白。常见的做法是先用一张loading图片占位,然后用异步的方式加载图片,等图片加载好了再把它填充到img节点里,这种场景就很适合使用虚拟代理。

先随便创建一个img对象

 1     var myImage = (function(){
 2         var imgNode = document.createElement( ‘img‘ );    //创建节点
 3         document.body.appendChild( imgNode );
 4         return {   //闭包返回一个对象,包含可以设置节点src属性的方法
 5             setSrc: function( src ){
 6                 imgNode.src = src;
 7             }
 8         }
 9     })();
10
11     myImage.setSrc( ‘真正的图片.jpg‘ );

这样的代码在网速很慢时,图片位置会出现较长时间空白。现在开始引入代理对象proxyImage,通过这个代理对象,在图片被真正加载好之前,页面中将出现一张占位图loading.gif,来提示用户图片正在加载。

 1     var myImage = (function(){
 2         var imgNode = document.createElement( ‘img‘ );   //同上
 3         document.body.appendChild( imgNode );
 4         return {
 5             setSrc: function( src ){
 6                 imgNode.src = src;
 7             }
 8         }
 9     })();
10
11     var proxyImage = (function(){
12         var img = new Image;         //创建一个图片对象
13         img.onload = function(){     //图片对象异步加载完成后,加载完成包含图片的加载完成,这句代码可以理解为一个监控
14             myImage.setSrc( this.src );  //把节点的src设置成图片对象的
15         }
16         return {
17             setSrc: function( src ){
18                 myImage.setSrc( ‘loading.gif‘ );   //预先给个占位图片
19                 img.src = src;       //给图片对象设置src属性
20             }
21         }
22     })();
23
24     proxyImage.setSrc( ‘真正的图片.jpg‘ );  //调用这个方法,会先给目标节点一个占位图片,同时内置的图片对象开始加载图片,当加载完成后,触发设置,节点显示正确图片

这里我们也可以看出,资源的加载只需要一次,只要这张图片下载好了,通过src都能很快显示它。

3.代理的意义

图片预加载功能不通过代理也可以实现,如下:

 1     var MyImage = (function(){
 2         var imgNode = document.createElement( ‘img‘ );  //创建节点
 3         document.body.appendChild( imgNode );
 4         var img = new Image;    //创建一个图片对象
 5         img.onload = function(){  //图片对象异步加载
 6             imgNode.src = img.src;   //节点更换src
 7         };
 8         return {
 9             setSrc: function( src ){
10                 imgNode.src = ‘loading.gif‘;   //预先给个占位图片
11                 img.src = src;    //图片对象设置src
12             }
13         }
14     })();
15
16     MyImage.setSrc( ‘真正的图片.jpg‘ );

看起来没什么问题,但是我们得提到一个面向对象设计的原则——单一职责原则。
一个类(通常也包括对象和函数等),应该仅有一个引起它变化的原因。如果一个对象承担了多项职责,就意味着这个对象将变得巨大,引起它变化的原因可能会有多个。如果一个对象承担的职责过多,等于把这些职责耦合到了一起,这种耦合会导致脆弱和低内聚的设计。当变化发生时,设计可能会遭到意外的破坏,我们可能不得不繁琐的修改代码或者重写函数。
比如上段代码中的MyImage对象除了负责给img节点设置src外,还要负责预加载图片。我们在处理其中一个职责时,有可能因为其强耦合性影响另外一个职责的实现。
我们需要的只是给img 节点设置src,预加载图片只是一个锦上添花的功能。

使用代理模式的代码中,我们并没有改变或者增加MyImage的接口,但是通过代理对象,实际上给系统添加了新的行为。这是符合开放—封闭原则的。给img节点设置src和图片预加载这两个功能,被隔离在两个对象里,它们可以各自变化而不影响对方。何况就算有一天我们不再需要预加载,那么只需要改成请求本体而不是请求代理对象即可。

4.代理和本体接口的一致性

如果有一天我们不再需要预加载,那么就不再需要代理对象,可以选择直接请求本体。其中关键是代理对象和本体都对外提供了setSrc 方法。这样做有两个好处,(1)使用者不会被api搞糊涂(2)在任何使用本体的地方都可以替换成使用代理。

特别:如果代理对象和本体对象都为一个函数(函数也是对象),函数必然都能被执行,则可以认为它们也具有一致的“接口”。

 1     var myImage = (function(){
 2         var imgNode = document.createElement( ‘img‘ );
 3         document.body.appendChild( imgNode );
 4         return function( src ){
 5             imgNode.src = src;
 6         }
 7     })();
 8
 9     var proxyImage = (function(){
10         var img = new Image;
11         img.onload = function(){
12             myImage( this.src );
13         }
14         return function( src ){
15             myImage( ‘loading.gif‘ );
16             img.src = src;
17         }
18     })();
19
20     proxyImage( ‘真正的图片.jpg‘ );

5.虚拟代理合并HTTP请求

前面高阶函数的时候提过节流函数,这里也是一样,为了避免可能频繁触发的请求导致的服务器压力,可以设置一个时间延迟,比如说2s,2s后会把请求一起提交一次。只不过现在这里通过代理来实施。

6.缓存代理

缓存代理的例子——计算乘积

 1     var mult = function(){    //计算乘积的函数
 2         console.log( ‘开始计算乘积‘ );
 3         var a = 1;
 4         for ( var i = 0, l = arguments.length; i < l; i++ ){  //遍历参数,计算结果
 5             a = a * arguments[i];
 6         }
 7         return a;   //返回结果
 8     };
 9
10     mult( 2, 3 ); // 输出:6
11     mult( 2, 3, 4 ); // 输出:24
12
13     var proxyMult = (function(){
14         var cache = {};   //利用闭包  保留结果,作为缓存
15         return function(){
16             var args = Array.prototype.join.call( arguments, ‘,‘ ); //把参数变成字符串
17             if ( args in cache ){   //如果这个字符串名称在缓存中存在
18                 return cache[ args ];    //返回结果
19             }
20             return cache[ args ] = mult.apply( this, arguments );   //不存在,就调用乘积函数计算一次,保存结果
21         }
22     })();
23
24     proxyMult( 1, 2, 3, 4 ); // 输出:24
25     proxyMult( 1, 2, 3, 4 ); // 输出:24   第二次调用,没有再次计算,而是拿的缓存中的结果

用高阶函数动态创建代理

通过传入高阶函数这种更加灵活的方式,可以为各种计算方法创建缓存代理。

 1     /**************** 计算乘积 *****************/
 2     var mult = function(){
 3         var a = 1;
 4         for ( var i = 0, l = arguments.length; i < l; i++ ){
 5             a = a * arguments[i];
 6         }
 7         return a;
 8     };
 9     /**************** 计算加和 *****************/
10     var plus = function(){
11         var a = 0;
12         for ( var i = 0, l = arguments.length; i < l; i++ ){
13             a = a + arguments[i];
14         }
15         return a;
16     };
17     /**************** 创建缓存代理的工厂 *****************/
18     var createProxyFactory = function( fn ){  //这里只是把最后调用的函数变成参数传进来而已
19         var cache = {};
20         return function(){
21             var args = Array.prototype.join.call( arguments, ‘,‘ );
22             if ( args in cache ){
23                 return cache[ args ];
24             }
25             return cache[ args ] = fn.apply( this, arguments );
26         }
27     };
28
29     var proxyMult = createProxyFactory( mult ),
30     proxyPlus = createProxyFactory( plus );
31     alert ( proxyMult( 1, 2, 3, 4 ) ); // 输出:24
32     alert ( proxyMult( 1, 2, 3, 4 ) ); // 输出:24
33     alert ( proxyPlus( 1, 2, 3, 4 ) ); // 输出:10
34     alert ( proxyPlus( 1, 2, 3, 4 ) ); // 输出:10

总结

代理模式包括许多小分类,在JavaScript 开发中最常用的是虚拟代理和缓存代理。代理模式可以理解为一个中转站,其内部必然会对本体进行调用。我们在编写业务代码的时候,不需要去预先猜测是否需要使用代理模式。当真正发现不方便直接访问某个对象的时候,再编写代理也不迟。

时间: 2024-10-16 19:24:05

javascript设计模式与开发实践阅读笔记(6)——代理模式的相关文章

javascript设计模式与开发实践阅读笔记(9)——命令模式

命令模式:有时候需要向某些对象发送请求,但是并不知道请求的接收者是谁,也不知道被请求的操作是什么,此时希望用一种松耦合的方式来设计软件,使得请求发送者和请求接收者能够消除彼此之间的耦合关系. 说法很复杂,简单来说就是希望真正做事情的对象不要直接被调用,当我们下达一些命令之后, 希望对象已经间接的执行了.这样做的好处是可以解耦,代码可以更为灵活,还可以管理命令,甚至完成命令队列这样的操作. 实现思路 为了实现这种效果,我们需要通过一个函数,创造一个接口对象,调用接口对象的方法,就是调用对象真正的方

javascript设计模式与开发实践阅读笔记(4)——单例模式

定义 单例模式:保证一个类仅有一个实例,并提供一个访问它的全局访问点. 具体来说,就是保证有些对象有且只有一个,比如线程池.全局缓存.浏览器中的window 对象等.在js中单例模式用途很广,比如登录悬浮窗,我希望无论我点击多少次这个浮窗都只会被创建一次,这里就可以用单例模式. 1.实现单例模式 思路:用一个变量来标志当前是否已经为某个类创建过对象,如果是,则在下一次获取该类的实例时,直接返回之前创建的对象:如果否就创建出那个对象. 1 var Singleton = function( nam

javascript设计模式与开发实践阅读笔记(3)——高阶函数的其他应用

高阶函数的其他应用 1.currying 函数柯里化,又称部分求值,一个currying 的函数首先会接受一些参数,接受了这些参数之后,该函数并不会立即求值,而是继续返回另外一个函数,刚才传入的参数在函数形成的闭包中被保存起来.待到函数被真正需要求值的时候,之前传入的所有参数都会被一次性用于求值. var cost = (function(){ var args = []; return function(){ if ( arguments.length === 0 ){ var money =

javascript设计模式与开发实践阅读笔记(7)——迭代器模式

迭代器模式:指提供一种方法顺序访问一个聚合对象中的各个元素,而又不需要暴露该对象的内部表示. 迭代器模式可以把迭代的过程从业务逻辑中分离出来,在使用迭代器模式之后,即使不关心对象的内部构造,也可以按顺序访问其中的每个元素. 流行语言如Java.Ruby 等都已经有了内置的迭代器实现,许多浏览器也支持JavaScript的Array.prototype.forEach. jQuery中的迭代器 1 $.each( [1, 2, 3], function( i, n ){ 2 console.log

javascript设计模式与开发实践 阅读笔记(1)

动态类型语言和静态类型语言的区别 根据数据类型的区别划分,静态语言在编译时已经确定变量的类型,动态语言在程序运行时,变量被赋予某个值之后,才具有某种类型. 静态语言在实际开发中为什么比动态语言繁琐 静态语言在编译时要进行类型检测,也就是说函数之类只能定好接收什么类型的变量.为了实现多态,可能的取值须放在一个类里面,函数接受这个类,而具体的变量则继承这个类的类型. 动态语言则不需要这么繁琐,因为他没有严格的类型检测,不过这样也会带来一些莫名其妙的问题.各有优缺点. 何为多态 接收不同参数,执行结果

javascript设计模式与开发实践阅读笔记(2)—— this,闭包与高阶函数

this this总是指向一个对象,有四种情况1. 作为对象的方法调用.2. 作为普通函数调用.3. 构造器调用.4. Function.prototype.call 或Function.prototype.apply 调用. 1. 作为对象的方法调用 当函数作为对象的方法被调用时,this 指向该对象: var obj = { a: 1, getA: function(){ alert ( this === obj ); // 输出:true alert ( this.a ); // 输出:

javascript设计模式与开发实践阅读笔记(5)——策略模式

策略模式:定义一系列的算法,把它们一个个封装起来,并且使它们可以相互替换. 我的理解就是把各种方法封装成函数,同时存在一个可以调用这些方法的公共函数.这样做的好处是可以消化掉内部的分支判断,使代码效率更高. 使用策略模式计算奖金 现在要实现这样一个东西,年终奖是根据员工的工资基数和年底绩效情况来发放的.例如,绩效为S的人年终奖有4倍工资,绩效为A的人年终奖有3倍工资,而绩效为B的人年终奖是2倍工资.假设财务部要求我们提供一段代码,来方便他们计算员工的年终奖. 思路一:创建一个函数,接收两个参数,

javascript设计模式与开发实践阅读笔记(10)—— 组合模式

组合模式:一些子对象组成一个父对象,子对象本身也可能是由一些孙对象组成. 有点类似树形结构的意思,这里举一个包含命令模式的例子 1 var list=function(){ //创建接口对象的函数 2 return { 3 arr:[], //执行列表 用来存储需要执行的对象 4 add:function(obj){ //往执行列表里添加对象 5 this.arr.push(obj); 6 }, 7 execute:function(){ //遍历执行列表,每个对象执行规定好的接口方法 8 fo

JavaScript设计模式与开发实践---读书笔记(7) 迭代器模式

迭代器模式是指提供一种方法顺序访问一个聚合对象中的各个元素,而又不需要暴露该对象的内部表示. JavaScript中的Array.prototype.foreach. 1.JQuery中的迭代器 $.each函数 2.自己实现一个each函数 var each = function(ary,callback){ for(var i=0,l=ary.length;i<l;i++){ callback.call(ary[i],i,ary[i]);//把下标和元素当作参数传给callback函数 }