什么是代理模式
代理模式是为一个对象提供一个代用品或占位符,以便控制对它的访问。
- 代理模式的关键是,当客户不方便直接访问一个对象或者不满足需要的时候,提供一个替身来控制对这个对象的访问,客户实际上访问的是替身对象。替身对象对请求做出一些处理之后,再把请求转交给本体对象。(例如:vue3 的双向数据绑定)
保护代理和虚拟代理
先举个例子:
现在我们假设小明对女生A进行表白,当A在心情好的时候收到花,小明表白的成功率为60%,当A心情差的时候收到花,小明表白的成功率无限接近于零。
小明和A刚认识2天,还无法分辨A什么时候心情好,如果不合时宜的把花送给A,被扔掉的可能性很大,但是A的朋友B却很了解A,所以小明只管把花交给B,B会监听A的心情变化,然后选择A心情好的时候将花交给A。
- 从上面的例子中我们可以简单了解到,代理B可以帮助代理A过滤掉一些请求,比如送花的人太胖,年纪太大或者没有宝马,这种请求可以在代理B直接被拒绝掉,这种叫做
保护代理
。 - 另外,假设现实中的花价格不非,同样程序世界中的new Flower也是一个代价昂贵的操作,那么我们可以把new Flower的操作交给代理B去执行,代理B会在A心情好的时候再执行new Flower,这是代理模式的另外一种形式,叫做
虚拟代理
。虚拟代理把一些开销很大的对象,延迟到真正需要它的时候才去创建。
虚拟代理实现图片预加载
常见的图片预加载通常是先用一张loading图片占位,然后用异步的方式加载图片,等图片加载好了以后再把它填充到img节点里面,这种场景很适合使用虚拟代理。
const myImage = (() => {
const imgNode = document.createElement('img');
document.body.appendChild( imgNode );
return {
setSrc: src => {
imgNode.src = src;
}
}
})()
const proxyImage = (() => {
const img = new Image;
img.onLoad = function() {
myImage.setSrc( this.src );
}
return {
setSrc: src => {
myImage.setSrc( ' .gif' );
img.src = src;
}
}
})()
proxyImage.setSrc("http://xxx.jpg")
代理的意义(简单的说提高代码的可读性和可维护性)
- 单一职责:一个类(函数或者对象)而言,应该仅有一个引起它可变化的原因。如果一个对象承担了多项职责,就意味着这个对象将变得巨大,引起它可变化的原因可能会有很多。面向对象设计鼓励将行为很不到细粒度的对象之中,如果一个对象承担了过多的职能,等于把这些职责耦合到一起,这种耦合会导致脆弱和低内聚的设计,当变化发生时,设计可能会遭受到以外的破坏。
- 在面向对象的程序设计中,大多数情况下,若违反其他任何原则,同时将违反开放封闭原则,如果我们自从网络上获取一些体积很小的图片,或者5年后网速很快不再需要预加载,我们希望删除掉预加载的代码,这时候就不得不改动myImage对象了。
- 实际上,我们只需要给img节点设置src,预加载图片只是为了显示效果,如果能把这个操作放到另一个对象里面,自然是一个非常好的方法。于是代理的作用就提现出来了,预加载的操作完成之后,把请求重新交给本体的MyImage。
- 这样我们并没有改变或者增加MyImage的接口,只是通过代理对象,实际上给系统添加了新的行为。这符合开放封闭原则的。给img节点设置src和图片预加载这两个功能,被隔离在两个对象里,他们可以各自变化而不影响对方。就算某天不再需要预加载只需要改成请求本体即可。
代理和本体接口的一致性
- 简单理解为了方便维护,保证接口的一致性可以在日后的维护中方便替换和取消代理。
虚拟代理合并HTTP请求
- 在vue和react中很多地方都有体现,延迟合并请求缓解服务器压力,节流防抖。
虚拟代理在惰性加载中的应用
- 类似我们的分页下拉加载,或者通过按钮点击再加载,以加快加载速度。
缓存代理
- 缓存代理可以为一些开销大的运算结果提供暂时的存储,在下次运算时,如果传递进来的参数跟之前一致,则可以直接返回前面存储的结果。
缓存代理的案例----计算乘积( 这里使用了闭包 )
const mult = function() {
console.log( '开始计算乘积')
let a = 1;
for ( let i = 0, i< arguments.length; i++ ){
a = a * arguments[i]
}
return a
}
const proxyMult = (function(){
let cache = {};
return function() {
let args = Array.prototype.join.call( arguments, "," );
if( args in cache ) {
return cache[ args ];
}
return cache[ args ] = mult.apply( this, arguments );
}
})()
缓存代理用于ajax 异步请求
- 这里就是我们常用的分页请求,同一页的数据理论上只需要去后台拉取一次,这些已经拉取到的数据被缓存在某个地方被缓存之后,下次再请求同一页面的时候,便可以直接使用之前的数据.
- 显然这里也可以引入缓存代理,实现方式和计算乘积的例子差不多,唯一不同的是请求数据是一个异步的操作,我们无法直接把计算结果放到代理的对象的缓存中,而是通过回调的方式。
- 在react,vue中就是我们常用state或者veux,mobx,redux状态管理工具存储我们的数据。
用高阶函数动态创建代理
- 通过传入高阶函数这种更加灵活的方式,可以为各种计算方法创建缓存代理。现在这些计算方法被当作参数传入专门用于创建缓存代理的工厂中,这样一来,我们就可以为乘法、加法、余数等创建缓存代理。
const createProxyFactory = function( fn ) {
let cache = {};
return function() {
let args = Array.prototype.join.call( arguments, "," );
if( args in cache ) {
return cache[ args ];
}
return cache[ args ] = fn.apply( this, arguments );
}
}
cosnt proxyPlus = createProxyFactory( plus )
cosnt proxyPlus = createProxyFactory( mult )