代理模式是为对象提供一个占位符或者代用品,以便控制对他的访问。
代理模式在生活中有许多场景,例如明星都有经纪人作为代理,如果想要请明星参加活动,只能联系他的经纪人,在薪酬和细节确认好之后,经纪人才会将合同拿个明星签字。
代理模式的关键是,当客户不方便直接访问一个对象或者不满足需要的时候,提供一个替身对象,用来控制对目标对象的访问,用户实际访问的是这个替换对象。
代理模式类型
保护代理
保护代理常用处理不同权限的对象对本地的访问,在 Javascript 中并不容易实现,因为无法判断是谁访问了对象。
虚拟代理
在程序中可能会有一些很昂贵的操作,可以通过设置虚拟代理,在真正需要它的时候才去执行。
以预加载图片为例:
- 不用代理实现
const MyImage = (function() {
const imgNode = document.createElement('img')
document.body.appendChild(imgNode)
var img = new Image()
img.onload = function(){
imgNode.src = img.src
}
return {
setSrc(src){
imgNode.src = './loading.gif'
img.src = src
}
}
})()
MyImage.setSrc('https://xxxx.x.png')
为了说明代理的意义,下面我们引入一个面向对象设计的原则——单一职责原则。
单一职责原则指的是,就一个类(通常也包括对象和函数等)而言,应该仅有一个引起它变化的原因。如果一个对象承担了多项职责,就意味着这个对象将变得巨大,引起它变化的原因可能会有多个。面向对象设计鼓励将行为分布到细粒度的对象之中,如果一个对象承担的职责过多,等于把这些职责耦合到了一起,这种耦合会导致脆弱和低内聚的设计。当变化发生时,设计可能会遭到意外的破坏。
职责被定义为“引发变化的原因”,上面的MyImage对象,除了负责给 img 节点设置src外,还负责预加载图片。在处理其中一个职责的时候,可能因为强耦合性而影响另一个职责。
- 通过代理实现,代理和本地接口需要保持一致
例如,假设某天不需要预加载了,用户可以选择直接请求本地。代理对于用于来说是透明的,用户并不清楚代理和本体的区别,用户可以放心的请求代理,他只需要关心结果是否符合预期,且在任何使用本土的地方都可以使用代理。
const myImage = (function(){
const imgNode = document.createElement('img')
document.body.appendChild(imgNode)
return function(src){
imgNode.src = src
}
}
})()
const proxyImage = (function(){
const img = new Image()
img.onload = function(){
myImage(this.src)
}
return function(src){
myImage('./loading.gif')
img.src = src
}
})()
proxyImage('https://xxxx.x.png')
虚拟代理合并请求
对于节流函数,并不陌生。为了避免请求导致的服务器压力,可以设置一个时间延迟,例如2s,2s 后会把请求一起提交。同样也可以通过虚拟代理的方式来实现。
虚拟代理在惰性加载中的应用
通过虚拟代理将操作缓存起来,等到真正加载资源的之后,再从缓存中读取,一次执行它们。
缓存代理
缓存代理可以为一些开销大的运算结果提供暂时的存储,在下次运行的时候,如果传递参数一直,则直接返回缓存中的执行结果。
缓存代理的例子——计算乘积
// 不使用缓存
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 ); // 输出:6
mult( 2, 3, 4 ); // 输出:24
// 加入缓存
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 ); // 输出:24
proxyMult( 1, 2, 3, 4 ); // 输出:24
用高阶函数动态创建代理
通过传入高阶函数可以为各种计算方法创建缓存代理
/**************** 计算乘积 *****************/
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 ) ); // 输出:24
alert ( proxyMult( 1, 2, 3, 4 ) ); // 输出:24
alert ( proxyPlus( 1, 2, 3, 4 ) ); // 输出:10
alert ( proxyPlus( 1, 2, 3, 4 ) ); // 输出:10
还有很多其他不常用的的代理模式,就不赘述了。在Javascript 中常用的是虚拟代理和缓存代理。虽然代理模式非常有用,但是我们在编写业务的时候并需要预先去猜测是否需要使用代理模式。当真正发现请求某个对象不方便的时候,再去写也不迟。