一个对象提供一个代用品或占位符,以便控制对它的访问。代理控制着对于原对象的访问, 并容许在将请求提交给对象先后进行一些处理。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
间接访问myImage
。proxyImage
控制了myImage
对图片的直接操做,在此过程当中加入一系列操做,而后再将处理好的请求转交给myImage
。网络
首先咱们引入一个面向对象设计的原则——单一职责原则。app
就一个类而言,应该只有一个引发它变化的缘由。若是一个对象承担了多项职责,这个对象就会趋向臃肿,引发它变化的缘由可能会有多个,也就等于把这些职责耦合在一块儿,这种耦合会致使脆弱和低内聚的设计。异步
而后再看以前写的程序,咱们并无改变或者增长myImage
的接口,可是经过代理对象给它添加了新的行为。这符合开放-封闭原则。给img
节点设置src
和图片预加载这两个功能隔离在两个对象中,他们能够各自变化而不影响对方。若是后期不须要预加载了,只须要请求本体而不是请求代理对象便可。优化
上面说到若是后期不须要预加载功能是,只用改为直接请求本体便可。其中关键是代理和本体都具备setSrcd
的接口。在客户看来,代理对象和本体对象是一致的。这样作有两个好处:this
在 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 复制代码
缓存代理在优化列表翻页的时候常常能够用到:将以前选中的页码的数据缓存起来,后续再筛选到这个页码时直接从缓存中读取。
代理模式很是有用,可是咱们在编写业务代码时每每不须要去预测是否须要使用搭理模式,当真正发现不方便的时候再引入代理模式也不迟。