代理模式是为一个对象提供一个代用品或占位符,以便控制对它的访问。
代理模式的关键是,当客户不方便直接访问一个对象或者不满足需要的时候,提供一个替身对象来控制对这个对象的访问,客户实际上访问的是替身对象。替身对象对请求做出一些处理后,再把请求转交给本体对象。
保护代理:代理B可以帮助代理A过滤掉一些请求,这种代理叫保护代理。
虚拟代理:虚拟代理把一些开销很大的对象,延迟到真正需要它的时候才去创建。虚拟代理是最常用的一种代理模式。
1.虚拟代理实现图片预加载
图片预加载是一种常用技术。
常见的做法是先用一张loading图片占位,然后用异步的方式加载图片,等图片加载好了再把它填充到img节点里,这种场景就很适合使用虚拟代理。
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('images/1.jpg');//本地图片 img.src = src; } } })(); proxyImage.setSrc('http://img04.sogoucdn.com/app/a/100520021/b173203599e22d825c5f905d1da92535');
代理的意义:
单一职责原则:就一个类(通常也包括对象和函数)而言,应该仅有一个引起它变化的原因。职责被定义为"引起变化的原因"。
实际上,我们需要的只是给img节点设置src,预加载图片只是一个锦上添花的功能。于是代理的作用在这里就体现出来了,代理负责预加载图片,预加载的操作完成之后,把请求重新交给本体MyImage。
代理和本体接口的一致性:
上述代码,其中的关键是代理和本体都对外提供了setSrc方法,在客户看来,代理对象和本体是一致的,代理接手请求的过程对于用户来说是透明的,用户不清楚代理和本体的区别。
这样有两个好处:
- 用户可以放心的请求代理,他只关心是否能得到想要的结果。
- 在任何使用本体的地方都可以替换成使用代理。
另外,如果代理对象和本体都为一个函数(函数也是对象),函数必然都能被执行,则可以认为它们也具有一致的"接口",代码如下:
var myImage = (function(){ var imgNode = document.createElement('img'); document.body.appendChild(imgNode); return { imgNode.src = src; } })(); var proxyImage = (function(){ var img = new Image; img.onload = function(){ myImage(this.src); } return function(src){ myImage.setSrc('images/1.jpg');//本地图片 img.src = src; } })(); proxyImage.setSrc('http://img04.sogoucdn.com/app/a/100520021/b173203599e22d825c5f905d1da92535');
2.虚拟代理合并HTTP请求
在Web开发中,也许最大的开销就是网络请求。
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>虚拟代理合并http请求</title> </head> <body> <input type="checkbox" id="1">1 <input type="checkbox" id="2">2 <input type="checkbox" id="3">3 <input type="checkbox" id="4">4 <input type="checkbox" id="5">5 <input type="checkbox" id="6">6 <input type="checkbox" id="7">7 <input type="checkbox" id="8">8 <input type="checkbox" id="9">9 </body> </html> <script> 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(function(){ synchronousFile(cache.join(','));//两秒后向本体发送需要同步的ID集合 clearTimeout(timer); //清空定时器 timer = null; cache.length = 0;//清空ID集合 },2000) } })(); var checkbox = document.getElementByTagName('input'); for(var i=0,c;c=checkbox[i++]; ){ c.onclick = function(){ if(this.checked === true){ proxysynchronousFile(this.id); } } }; </script>
3.虚拟代理在惰性加载中的应用
4.缓存代理
缓存代理可以为一些开销大的运算结果提供暂时的存储,在下次计算时,如果传进来的参数跟之前一致,则可以直接返回前面存储的运算结果。
例子:1.计算乘积
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(){ var cache = {}; return function(){ var 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); proxyMult(1,2,3,4);//第二次调用proxyMult时,本体mult函数并没有被计算,proxyMult直接返回了之前缓存好的计算结果
2.缓存代理用于ajax异步请求数据
5.用高阶函数动态创建代理
可以为各种方法创建缓存代理。
var mult = function(){ var a = 1; for( var i=0,l=arguments.length;i<l;i++){ a = a*arguments[i]; } return a; }; var plus = function(){ var a = 0; for( var i=0,l=arguments.length;i<l;i++){ a = a+arguments[i]; } return a; }; var createProxyFactory = function(fn){ var cache = {}; return function(){ var args = Array.prototype.join.call(arguments,','); if(args in cache){ return cache[args]; } return cache[args] = fn.apply(this,arguments); } }; var proxyMult = createProxyFactory(mult), proxyPlus = createProxyFactory(plus); alert(proxyMult(1,2,3,4)); alert(proxyMult(1,2,3,4)); alert(proxyPlus(1,2,3,4)); alert(proxyPlus(1,2,3,4));
6.其他代理模式
- 防火墙代理:控制网络资源的访问。
- 远程代理
- 保护代理
- 智能引用代理
- 写时复制代理:通常用于复制一个庞大对象的情况。
7. 小结
JavaScript开发中最常用的是虚拟代理和缓存代理。