代理模式
不方便直接访问某个对象时,使用代理对象来进行访问。
虚拟代理
预加载图片
假设我们有这样一个需求:我们需要加载很多图片,期望在图片被下载之前展示loading效果,当图片下载后展示图片。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 const myImage = (function ( ) { const imgNode = document .createElement ('img' ); document .body .appendChild (imgNode); const img = new Image () img.onload = function (params ) { imgNode.src = this .src ; } return { setSrc : function (src ) { imgNode.src = './bg.jpg' ; img.src = src } } })(); myImage.setSrc ("https://jing-jiu.github.io/jing-jiu/img/avatar.jpg" );
对于我们来说,把图片加载出来是我们主要的目的,而做预加载,loading只是优化的操作,因此我们应该:
将预加载图片和设置图片的src这两个功能分离开来
同时我们期望不管是直接给图片设置src还是经过预加载,二者的调用方式是一致的。
对于下面的代码,当我们不需要预加载图片时,我们只需要将proxyImage替换为myImage。但是显然,代理模式的代码量比不使用代理模式会多很多,但是在大型项目中这样的设计是非常有必要的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 const myImage = (function ( ) { const imgNode = document .createElement ('img' ); document .body .appendChild (imgNode); return { setSrc : function (src ) { imgNode.src = src; } } })();const proxyImage = (function ( ) { const img = new Image ; img.onload = function ( ) { myImage.setSrc (this .src ); } return { setSrc : function (src ) { myImage.setSrc ('loading.gif' ); img.src = src; } } })(); proxyImage.setSrc ("https://jing-jiu.github.io/jing-jiu/img/avatar.jpg" );
合并HTTP请求
假设我们在做一个文件同步的功能,当我们选中一个 checkbox 的时候,它对应的文件就会被同步到另外一台备用服务器上面。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 <div class ="upload-item" > <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</div > <script > const synchronousFile = function (id ) { console .log ('开始同步文件,id 为: ' + id); }; const checkbox = document .getElementsByTagName ('input' ); for (let i = 0 , c; c = checkbox[i++];) { c.onclick = function ( ) { if (this .checked === true ) { synchronousFile (this .id ); } } }; </script >
我们期望可以收集几秒内的所有选中文件,统一发请求给后端,这样可以大大减少请求的次数。(在对于实时性要求不高的场景完全是可行的)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 <div class ="upload-item" > <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</div > <script > const synchronousFile = function (id ) { console .log ('开始同步文件,id 为: ' + id); }; const proxySynchronousFile = (function ( ) { let timer = null const cache = new Set () return (id ) => { cache.add (id) if (timer) { return } timer = setTimeout (() => { synchronousFile ([...cache].join ("," )) clearTimeout (timer) timer = null cache.clear () }, 2000 ) } })() const checkbox = document .getElementsByTagName ('input' ); for (let i = 0 , c; c = checkbox[i++];) { c.onclick = function ( ) { for (let i = 0 , c; c = checkbox[i++];) { if (c.checked === true ) { proxySynchronousFile (c.id ); } } } }; </script >
惰性加载
跟加载图片类似,有一些脚本是当我们打开控制台时才会执行,因此我们不需要一开始就加载这些脚本, 而是在用户按下F12打开控制台时加载脚本 。但是需要注意,我们一开始没有加载脚本,那么在代码中调用这些未加载的脚本的方法就会出现问题,因此我们需要:
一个代理对象,帮我们代理需要调用的方法,在脚本加载成功后执行脚本上的方法。
并且我们希望重复按下F12时脚本只被加载一次。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 const proxyConsole = (function ( ) { const cache = [] const handler = function (e ) { if (e.keyCode === 113 ) { const script = document .createElement ("script" ) script.onload = () => { for (let i = 0 , fn; fn = cache[i++];) { fn () } } script.src = 'remoteConsole.js' document .getElementsByTagName ("head" )[0 ].appendChild (script) document .body .removeEventListener ("keydown" , handler) } }; document .body .addEventListener ("keydown" , handler) return { log ( ) { const args = arguments ; cache.push (() => { return remoteConsole.log .apply (remoteConsole, args); }); } } })() proxyConsole.log ('xxx' ); remoteConsole = { log : function ( ) { console .log (Array .prototype .join .call (arguments )); } };
缓存代理
有点类似于算法中的哈希表,将结果缓存起来,下次遇到同样的请求直接返回。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 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 );
其他代理模式
防火墙代理:控制网络资源的访问,保护主题不让“坏人”接近。
远程代理:为一个对象在不同的地址空间提供局部代表,在 Java 中,远程代理可以是另一个虚拟机中的对象。
保护代理:用于对象应该有不同访问权限的情况。
智能引用代理:取代了简单的指针,它在访问对象时执行一些附加操作,比如计算一个对象被引用的次数。
写时复制代理:通常用于复制一个庞大对象的情况。写时复制代理延迟了复制的过程,当对象被真正修改时,才对它进行复制操作。写时复制代理是虚拟代理的一种变体,DLL (操作系统中的动态链接库)是其典型运用场景。