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

  • 相关阅读:
    JS DOM编程艺术——DOM获取元素—— JS学习笔记2015-7-6(第77天)
    JS DOM编程艺术的学习—— JS学习笔记2015-7-5(第76天)
    面向对象的程序设计1 理解对象—— JS学习笔记2015-7-4(第75天)
    JS 引用类型 Math 对象 JS学习笔记2015-7-3(第74天)
    JS 引用类型和值类型
    HTML5之应用缓存---manifest---缓存使用----Web前端manifest缓存
    易懂 易上手的cookie 最简单明了 js中cookie的使用方法及教程
    iOS开发-面试总结(十一)
    iOS开发-面试总结(十)
    iOS开发-面试总结(九)
  • 原文地址:https://www.cnblogs.com/grey-zhou/p/6088157.html
Copyright © 2020-2023  润新知