JavaScript设计模式—代理模式

定义

一个对象提供一个代用品或占位符,以便控制对它的访问。代理控制着对于原对象的访问, 并容许在将请求提交给对象先后进行一些处理。html

真实世界类比

代理模式

信用卡是银行帐户的代理, 银行帐户则是一大捆现金的代理。 它们都实现了一样的接口, 都可用于进行支付。 消费者会很是满意, 由于没必要随身携带大量现金; 商店老板一样会十分高兴, 由于交易收入能以电子化的方式进入商店的银行帐户中, 无需担忧存款时出现现金丢失或被抢劫的状况。web

模式实现-图片预加载(懒加载)

功能需求:用一张loading图占位,而后用异步的方式加载图片,等图片加载好了再把它填充到 img 节点里。缓存

首先建立一个本体对象,负责往页面中建立 img 标签,并提供一个setSrc接口,用于外界设置 img 的src属性:服务器

const myImage = (function() {
  const imgNode = document.createElement("img");
  document.body.appendChild(imgNode);
  return {
    setSrc: function(src) {
      imgNode.src = src;
    }
  };
})();

myImage.setSrc("http://qiniu.johnsenzhou.com/FmC3WzznEuwQwhEKn9YWn43phArJ");
复制代码

如今开始引入代理对象proxyImage,在图片没加载出来前用loading图占位,提示用户图片正在加载。markdown

const proxyImage = (function() {
  const img = new Image();
  img.onload = function() {
    myImage.setSrc(this.src);
  };
  return {
    setSrc: function(src) {
      myImage.setSrc(
        "http://www.sucaijishi.com/uploadfile/2015/0210/20150210104951657.gif"
      );
      img.src = src;
    }
  };
})();

proxyImage.setSrc("http://qiniu.johnsenzhou.com/FmC3WzznEuwQwhEKn9YWn43phArJ");
复制代码

如今咱们经过proxyImage间接访问myImageproxyImage控制了myImage对图片的直接操做,在此过程当中加入一系列操做,而后再将处理好的请求转交给myImage网络

代理的意义

首先咱们引入一个面向对象设计的原则——单一职责原则。app

就一个类而言,应该只有一个引发它变化的缘由。若是一个对象承担了多项职责,这个对象就会趋向臃肿,引发它变化的缘由可能会有多个,也就等于把这些职责耦合在一块儿,这种耦合会致使脆弱和低内聚的设计。异步

而后再看以前写的程序,咱们并无改变或者增长myImage的接口,可是经过代理对象给它添加了新的行为。这符合开放-封闭原则。给img节点设置src和图片预加载这两个功能隔离在两个对象中,他们能够各自变化而不影响对方。若是后期不须要预加载了,只须要请求本体而不是请求代理对象便可。优化

代理和本体接口的一致性

上面说到若是后期不须要预加载功能是,只用改为直接请求本体便可。其中关键是代理和本体都具备setSrcd的接口。在客户看来,代理对象和本体对象是一致的。这样作有两个好处:this

  • 用户能够放心的使用请求代理,而不须要弄清楚二者之间的区别。他只关系获得的结果是否一致。
  • 在任何使用本体的地方均可以放心的替换成使用代理。

模式实现-合并 HTTP 请求

在 web 开发中,也许最大的开销就是网络请求。假设咱们在作一个文件同步功能,当咱们每选择一个CheckBox,他对应的文件就同步到另外一台服务器上,以下图所示:

文件上传

首先咱们先放置好checkbox节点:

<div>
  <input type="checkbox" id="1" />
  <input type="checkbox" id="2" />
  <input type="checkbox" id="3" />
  <input type="checkbox" id="4" />
  <input type="checkbox" id="5" />
  <input type="checkbox" id="6" />
  <input type="checkbox" id="7" />
</div>
复制代码

而后肯定代理对象处理逻辑:

收集 2 秒内的用户请求,等待 2 秒之后再把这 2 秒内须要同步的文件打包发给服务器。

const uploadFile = id => {
  console.log("开始上传文件,id为", id);
};

const proxyPploadFile = (function() {
  const cache = [];
  let timer;
  return function(id) {
    cache.push(id);
    if (timer) return; // 保证不覆盖已启动的定时器
    timer = setTimeout(() => {
      uploadFile(cache.join(","));
      clearTimeout(timer);
      timer = null;
      cache.length = 0;
    }, 2000);
  };
})();

const checkboxs = document.getElementsByTagName("input");
checkboxs.forEach(item => {
  item.onclick = function() {
    if (this.checked === true) {
      proxyPploadFile(this.id);
    }
  };
});
复制代码

缓存代理

缓存代理可暂存一些开销大的运算结果,以便于下次运行一样的参数时直接返回结果。

计算乘积

const mult = function() {
  let result = 1;
  for (let i = 0; i < arguments.length; i++) {
    result = result * arguments[i];
  }
  return result;
};

const 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
复制代码

缓存代理在优化列表翻页的时候常常能够用到:将以前选中的页码的数据缓存起来,后续再筛选到这个页码时直接从缓存中读取。

小结

代理模式很是有用,可是咱们在编写业务代码时每每不须要去预测是否须要使用搭理模式,当真正发现不方便的时候再引入代理模式也不迟。

相关文章
相关标签/搜索