一、定义
当客户不方便直接访问一个对象或者不满足需要的时候,提供一个对象来控制堆这个对象的访问。
二、举例
惰性单例模式的实现依靠缓存代理
三、结构
代理模式需要一个本体对象和一个代理对象。在代理模式下,对于本体对象的特定的操作通过代理对象进行。如图所示
这种模式的关键点在于:本体对象和代理对象接口的一致性。也就是说如果需要不通过代理进行操作,那么直接操作本体对象依然可以。
四、实现
代理模式分为很多类,其中经常用到的有保护代理、虚拟代理、缓存代理。
1.保护代理
保护代理的作用是当对本体对象的属性进行访问和赋值时,代理对象可以对其进行拦截。
在ES6中,代理是一个已经被实现的知识点,如下:
var obj = { _name: "JYY", age: 29};var objProxy = new Proxy(obj, { get: function(target, key){ if(key === "_name") return undefined; return target[key]; }, set: function(target, key){ if(key === "_name") return; } }); objProxy._name; // undefinedobjProxy.age; // 29
上面的代码中obj是本体对象,objProxy是代理对象,这个代理实现了禁止访问和设置内部私有属性的功能。在这段代码中,我们使用代理对象对本体对象的get和set方法进行了代理,本体对象和代理对象都包含了这个set和get接口(obj对象本身内部实现了get和set),所以如果我们绕过代理对象直接访问和赋值本体对象也是可以的:
var obj = { _name: "JYY", age: 29}; console.log(obj._name); //JYYobj._name = "hah"; console.log(obj._name); // hah
保护代理的重点在于,代理对象保护外界对于本体对象的可访问和可操作性,也就是说在保护代理中,代理对象是用于禁止外界对本体对象的操作,防止本体对象的属性被外界进行操作
2.虚拟代理
虚拟代理在我理解,就是用户认为已经执行了某个功能,事实上却时使用代理对象进行占位,待触发的时机到来,才会真正的执行本体对象的操作。也就是虚拟代理把一些开销很大的对象,延迟到真正需要他的时候才采取创建。因此虚拟代理的使用伴随的是性能的提升。
典型的虚拟代理的例子就是节流,如下所示:
var resizeProxy = function(fn){ let timer = null; return function(){ if(!timer){ timer = setTimeout(function(){ fn && fn(); timer = null; },1000) } } };var resizeChange = function(){ console.log(1); }; window.onresize = resizeProxy(resizeChange);
这段代码是经典的使用节流控制窗体尺寸大小改变事件触发的例子,作用就是让resize事件的触发不那么频繁,自定义的控制触发的频率,这对于性能的提升很有帮助。
在这段代码里面,resizeChange就是本体,resizeProxy就是代理,如果不使用代理直接将fn复制给window.onresize依旧是可用的。而使用代理的作用就是首先进行占位,开发中代理内部的代码是不可见的(比如提供的是api),利用resizeProxy的不可见性,让客户认为已经调用了功能代码,但是事实上是我们并未立即执行代码,因为我们作为开发者知道立即执行会带来性能的丧失,只有在合适的时机我们才会委托代理执行本地代码,执行真正的业务代码。
3.缓存代理
缓存代理在单例模式中就已出现,用于创建惰性代理模式,其原理就是在需要时创建对象,并将该对象保存在闭包中,这样可以一次创建多次使用。
当然在惰性单例中使用是缓存代理最简单的实现方式。在实际开发中,我们可能会遇到这样的场景,在tab中每个tab页都包含多个图表,因此在点击tab的时候就需要获取这个tab对应的图表的数据,这些数据都需要从后端请求。比如以某个省的地市经济统计情况为例,我们需要的页面如下:
可见这样的情况下,用户很可能不断地点击tab切换城市,然后和其他城市进行对比。如果每次点击都请求数据,带给用户的体验会非常差。这样的情况下,我们就可以做个全局的缓存,当然你可以保存在组件的状态中,除此之外缓存代理也是个很棒的选择。代码如下:
// 请求数据function requestData(regionName){ return new Promise(function(resolve, reject){ var rest = ajax({ type: "post", params: {regionName: regionName} }) resolve(rest); }) }// 初始化数据代理方法var proxyInit = (function(){ var cache = {}; return async function(regionName){ if(cache[regionName]){ return cache[regionName]; }else{ return cache[regionName] = await requestData(regionName); } } }); proxyInit("石家庄");
五、总结
三种类型虽然均为代理模式,但是各自的目的并不相同,保护代理是为了阻止外部对内部对象的访问或者是操作等;虚拟代理是为了提升性能,延迟本体执行,在合适的时机进行触发,目的是减少本体的执行次数;缓存代理同样是为了提升性能,但是为了减缓内存的压力,同样的属性,在内存中只保留一份。