美文网首页
代理模式

代理模式

作者: 般犀 | 来源:发表于2019-01-03 17:29 被阅读0次

    代理模式就是 A 要对 C 做一件事,但是他可能不方便直接对 C 做这件事,A 就拜托 B,让 B 帮 A 做这件事,这就是代理。

    书中举了一个小明想送花给 A,但是又不好意思,而且不好估计 A 什么时候心情好(心情好送花更能得到好感)才送花,于是小明拜托 A 的闺蜜 B,让 B 帮小明送花。 B 因为熟悉 A,所以可以在检测到 A 心情好的时候将花交给 A:

    var Flower = function(){};
    var xiaoming = {
        sendFlower: function( target){
        var flower = new Flower();
        target.receiveFlower( flower );
      }
    };
    var B = {
      receiveFlower: function( flower ){
          A.listenGoodMood(function(){ // 监听A 的好心情
          A.receiveFlower( flower );
        });
      }
    };
    var A = {
      receiveFlower: function( flower ){
        console.log( '收到花 ' + flower );
      },
      listenGoodMood: function( fn ){
          setTimeout(function(){ // 假设10 秒之后A 的心情变好
          fn();
        }, 10000 );
      }
    };
    xiaoming.sendFlower( B );
    

    保护代理和虚拟代理

    保护代理类似过滤器/拦截器一样的功能,就好像 B 可以帮 A 过滤掉一些不合理的送花请求,比如送花的人年纪太大或不合适,B可以不接受这个请求。
    另外,假设现实中花的价格不菲,导致在程序世界里,每实例化一朵花都是很大的开销,那么我们可以把new Flower()放在代理 B 中进行,代理 B 会在 A 心情好的时候再实例出花送给 A 。这就是代理模式的另一种模式:虚拟代理。虚拟代理把一些
    开销很大的操作延迟到真正需要它的时候才创建。
    (怎么听着像“惰性代理”更合适):

    var B = {
      receiveFlower: function(flower) {
        A.listenGoodMood(function() {
            var flower = new Flower();
            A.receiveFlower(flower);
        })
      }
    }
    

    虚拟代理实现图片预加载

    var myImage = (function() {
        var imgNode = document.createElement('img');
        document.body.appendChild(imgNode);
        return {
          setSrc: function(src) {
            imgNode.src = src;
          }
        }
      })();
      
      var proxyImage = (function() {
        var img = new Image;
        img.onload = function() {
          myImage.setSrc(this.src);
        }
        return {
          setSrc: function(src) {
            myImage.setSrc('./image/Loading_icon.gif');
            img.src = src;
          }
        }
      })()
      proxyImage.setSrc('http://qnimate.com/wp-content/uploads/2014/03/images2.jpg');
    

    代码的运行流程是:

    1. myImage 立即调用,只做了一件事:把一个空的 <img>元素插入 html 中,并返回一个对象,对象的 setSrc方法可以把图片的 src 修改为传入的参数。
    2. proxyImage 立即调用,做了两件事:实例化出一个 img对象,绑定 img 的 onload对象,当图片加载完成后,给图片设置新的 src
    3. pxoxyImagesetSrc做了两件事:调用myImage.setSrc设置空img元素的src为一张本地图片;给imgsrc赋值为传入的图片地址。
    4. onload事件触发,myImage.setSrc(this.src);再次被执行,<img>的src被替换。

    img 的onload的执行时机:看代码的时候对程序的运行有点疑惑,原因是不清楚 onload的执行时机,一番实验,发现在 Chrome 下,只要Image实例出来的imgsrc属性被赋值并且图片加载完成,onload就会触发。Image实例出来的对象作用可能就在于结合onload事件,对img的src进行赋值的时候可以给 html 上的 <img>元素执行一些操作。这说明我之前老想着的“ Image实例出来的img难道要插入 html 里?”的想法是有问题的。

    虚拟代理合并 HTTP 请求

    考虑如下场景:有一个有若干 checkbox 表单项的表单,每选中一个 checkbox 都向服务器发送一次请求。如果用户以极高的频率不断选中 checkbox 的话,就会产生大量的 HTTP 请求,给服务器增加负担:

    // html
    <body>
      <input type="checkbox" id="1"></input>1
      <input type="checkbox" id="2"></input>2
      <input type="checkbox" id="3"></input>3
      <input type="checkbox" id="4"></input>4
      <input type="checkbox" id="5"></input>5
      <input type="checkbox" id="6"></input>6
      <input type="checkbox" id="7"></input>7
      <input type="checkbox" id="8"></input>8
      <input type="checkbox" id="9"></input>9
    </body>
    
    // js
    var synchronousFile = function (id) {
      console.log('开始同步文件,id 为: ' + id);
    };
    var checkbox = document.getElementsByTagName('input');
    for (var i = 0, c; c = checkbox[i++];) {
      c.onclick = function () {
        if (this.checked === true) {
          synchronousFile(this.id);
        }
      }
    };
    

    这个时候增加一个代理函数,对请求函数synchronousFile()做封装,规定每两秒内的请求放在一个 HTTP 请求中。做一个节流:

    var synchronousFile = function (id) {
          console.log('开始同步文件,id 为: ' + id);
        };
        var proxySynchronousFile = (function () {
          var cache = [], // 保存一段时间内需要同步的 ID
            timer; // 定时器
          return function (id) {
            cache.push(id);
            if (timer) {
              return;
            }
    
            timer = setTimeout(() => {
              synchronousFile(cache.join(','));
              clearTimeout(timer);
              timer = null;
              cache.length = 0;
            }, 2000);
          }
        })();
    
        var checbox = document.getElementsByTagName('input');
        for (var i = 0, c; c = checbox[i++];) {
          c.onclick = function() {
            if (this.checked === true) {
              proxySynchronousFile(this.id);
            }
          }
        }
    

    发现一个问题,书里有很多的立即执行函数,但是在我的开发中很少意识到运用 IIFE 去编写函数。IIFE 可以产生闭包,使用场景就是:当一个函数需要被反复执行多次,但是每次执行的结果要叠加在上N次的执行结果上的时候。可能当我需要这个场景的时候,我都是把需要保存的结果放在了全局中,从而实现闭包可以实现的效果。

    缓存代理

    缓存代理可以为一些开销大的运行结果提供暂时的储存,在下次计算时如果传递的参数跟之前一致,可以直接返回前面存储的运算结果。
    以一个计算乘积的函数为例子:

    var mult = function() {
        console.log('开始计算乘积');
        var a = 1;
        for (var i = 0, l = arguments.length; i < l; i++) {
          a = a * arguments[i];
        }
        return a;
      }
    
      mult(2, 3);
      mult(2, 3, 4);
    
      var proxyMult = function() {
        const cache = {};
        return function() {
          const args = Array.prototype.join(',').call(arguments, ',');
          if (args in cache) {
            return cache[args];
          }
          return cache[args] = mult.apply(this, arguments);
        }
      }();
    
      proxyMult(1, 2, 3, 4) // 输出:24
      proxyMult(1, 2, 3, 4) // 输出:24
    

    这个方法虽然还有点问题,第一是防止缓存的结果过多,第二是他将传入的参数转为字符串后保存在对象中,如果传入的参数相同,但是顺序换了,那么cache就要保存多个相同参数的计算结果了,有个方法是先将传入的参数排序,然后再存入cache中。

    代理模式的应用场景:

    • 计算结算可以被缓存再利用时,将结果缓存在代理函数中
    • 是用户操作但是不需要马上执行时,把执行队列缓存在代理函数中,真正需要用到 时候再依次执行

    相关文章

      网友评论

          本文标题:代理模式

          本文链接:https://www.haomeiwen.com/subject/aexqrqtx.html