代理模式是为一个对象提供一个代用品或占位符,以便控制对它的访问
代理模式的关键是,当客户不方便直接访问一个对象或者不满足需要的时候,提供一个替身对象来控制对这个对象的访问,客户实际上访问的是替身对象。替身对象对请求做出一些处理之后,再把请求转交给本体对象。
情景:小明想送花给A(本体对象),B(替身对象)是A的好朋友,于是小明让B代替自己送花给A
保护代理
替身对象帮助本体对象过滤掉一些请求(本体对象不想处理的请求),这种代理叫做保护代理。保护代理用于控制不同权限的对象对目标对象的访问,但在 JavaScript 并不容易实现保护代理,因为我们无法判断谁访问了某个对象。
虚拟代理 常用
虚拟代理把一些开销很大的对象,延迟到真正需要它的时候才去创建。
虚拟代理实现图片预加载
在 Web 开发中,图片预加载是一种常用的技术,如果直接给某个 img 标签节点设置 src 属性,由于图片过大或者网络不佳,图片的位置往往有段时间会是一片空白。常见的做法是先用一张loading 图片占位,然后用异步的方式加载图片,等图片加载好了再把它填充到 img 节点里,这种场景就很适合使用虚拟代理。
代理的意义
为了说明代理的意义,下面我们引入一个面向对象设计的原则——单一职责原则。
单一职责原则指的是,就一个类(通常也包括对象和函数等)而言,应该仅有一个引起它变
化的原因。如果一个对象承担了多项职责,就意味着这个对象将变得巨大,引起它变化的原因可能会有多个。面向对象设计鼓励将行为分布到细粒度的对象之中,如果一个对象承担的职责过多,等于把这些职责耦合到了一起,这种耦合会导致脆弱和低内聚的设计。当变化发生时,设计可能会遭到意外的破坏。
保持代理和本体接口的一致性
如果有一天我们不再需要预加载,那么就不再需要代理对象,可以选择直接请 求本体。其中关键是代理对象和本体都对外提供了 setSrc 方法
提供相同方法,保持接口一致有以下好处:
- 用户可以放心地请求代理,他只关心是否能得到想要的结果。
- 在任何使用本体的地方都可以 被替换使用。
虚拟代理合并HTTP请求
在现实中的场景: 每周我们都要写一份工作周报,周报要交给总监批阅。总监手下管理着 150 个员工,如果我们每个人直接把周报发给总监,那总监可能要把一整周的时间都花在查看邮件上面。现在我们把周报发给各自的组长,组长作为代理,把组内成员的周报合并提炼成一份后一次性地发给总监。这样一来,总监的邮箱便清净多了。
在程序中的场景:在 Web 开发中,也许最大的开销就是网络请求。假设我们在做一个文件同步的功能,当我们选中一个 checkbox 的时候,它对应的文件就会被同步到另外一台备用服务器上面,我们可以通过一个代理函数proxySynchronousFile 来收集一段时间之内的请求,最后一次性发送给服务器。
缓存代理 常用
缓存代理可以为一些开销大的运算结果提供暂时的存储,在下次运算时,如果传递进来的参数跟之前一致,则可以直接返回前面存储的运算结果。
缓存代理的例子
计算乘积
// 创建一个用于求乘积的函数:
var mult = function() {
console.log('开始计算乘积');
var a=1;
for(let i=0,len=arguments.length;i<len;i++) {
a = a*arguments[i];
}
return a;
}
mult( 2, 3 ); // 6
mult( 2, 3, 4 ); //24
//加入缓存代理函数
var proxyMult = (function(){
var cache = {};// 运算过后格式{1,2,3,4: 24}
return function(){
var args = Array.prototype.join.call( arguments, ',' );
if ( args in cache ){
console.log('已经计算过,从缓存中取值:',args,cache)
return cache[ args ];
}
console.log('未计算过,新赋值缓存到cache对象:',args,cache)
return cache[ args ] = mult.apply( this, arguments );
}
})();
proxyMult( 1, 2, 3, 4 ); // 输出:24
缓存代理用于ajax异步请求数据
我们在常常在项目中遇到分页的需求,同一页的数据理论上只需要去后台拉取一次,这些已经拉取到的数据在某个地方被缓存之后,下次再请求同一页的时候,便可以直接使用之前的数据。 代码实现参照计算乘积例子
用高阶函数动态创建代理
/**************** 计算乘积 *****************/
var mult = function() {
console.log('开始计算乘积');
var a=1;
for(let i=0,len=arguments.length;i<len;i++) {
a = a*arguments[i];
}
return a;
}
/**************** 计算加和 *****************/
var plus = function() {
console.log('开始计算加和');
var a=0;
for(let i=0,len=arguments.length;i<len;i++) {
a = a+arguments[i];
}
return a;
}
/**************** 创建缓存代理的工厂函数 *****************/
var createProxyFactory = function(fn) {
var cache = {};// 运算过后格式{1,2,3,4: 24}
return function(){
var args = Array.prototype.join.call( arguments, ',' );
if ( args in cache ){
console.log(fn.name,'已经计算过,从缓存中取值:',args,cache)
return cache[ args ];
}
console.log(fn.name,'未计算过,新赋值缓存到cache对象:',args,cache)
return cache[ args ] = fn.apply( this, arguments );
}
}
var proxyMult = createProxyFactory( mult ),
proxyPlus = createProxyFactory( plus );
console.log ( proxyMult( 1, 2, 3, 4 ) ); // 输出:24
console.log ( proxyMult( 1, 2, 3, 4 ) ); // 输出:24
console.log ( proxyPlus( 1, 2, 3, 4 ) ); // 输出:10
console.log ( proxyPlus( 1, 2, 3, 4 ) ); // 输出:10
小结
代理模式包括许多小分类,在 JavaScript 开发中最常用的是虚拟代理和缓存代理。虽然代理 模式非常有用,但我们在编写业务代码的时候,往往不需要去预先猜测是否需要使用代理模式。 当真正发现不方便直接访问某个对象的时候,再编写代理也不迟。