代理模式

代理模式就是 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中。

代理模式的应用场景:

  • 计算结算可以被缓存再利用时,将结果缓存在代理函数中
  • 是用户操作但是不需要马上执行时,把执行队列缓存在代理函数中,真正需要用到 时候再依次执行
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

友情链接更多精彩内容