定义
代理模式是为一个对象提供一个代用品或占位符,以便控制对它的访问。
比如说,明星都有经纪人作为代理,如果想请明星来办一场商业演出,只能联系他的经纪人。经纪人会把商业演出的细节和报仇都谈好之后,再把合同交给明星签。
代理模式的关键是,当客户不方便直接访问一个对象或者不满足需要时,提供一个替身对象来控制对这个对象的访问,客户实际上访问的是替身对象。替身对象对请求做出一些处理后,再把请求转交给本体。
虚拟代理
虚拟代理把一些开销很大的对象,延迟到真正需要它的时候才去创建。
虚拟代理实现图片预加载
在Web开发中,图片预加载是一种常见的技术,如果直接给某个img标签节点设置src属性,由于图片过大或网络不佳,图片的位置往往有段时间会是一片空白的。常见的做法是用一张loading图片占位,任何等图片加载好了再把它填充进img节点中,这种场景就很适合用虚拟代理
首先创建一个普通的本地对象,这个对象负责往页面中创建一个img标签,并且对外提供setSrc接口,外界调用这个接口,便可以给img设置src
class MyImage {
constructor() {
this.img = document.createElement('img');
document.body.appendChild(this.img);
}
setSrc(src) {
this.img.src = src;
}
}
执行const myImage=new MyImage()生成一个img节点插入body中,然后执行setSrc('xxx'),这样的常规写法,在图片被加载好之前,页面会有一段长时间的空白期。
现在引入代理对象ProxyImage类,提供这个代理对象,在图片被真正加载好之前,页面中将会出现一张占位的加载图。
class ProxyImage {
constructor() {
this.myImage = new MyImage();
this.image = new Image();
this.image.onload = function () {
this.myImage.setSrc(this.image.src);
}
}
setSrc(src) {
this.myImage.setSrc('./loading.gif');
this.image.src = src;
}
}
const proxyImage = new ProxyImage();
proxyImage.setSrc(xxxx);
提供ProxyImage代理类间接地访问MyImage类实例化生成的对象,ProxyImage控制了对MyImage实例化对象的访问,并且在此过程中加入了一些额外的操作,在图片真正加载好之前去设置一张本地的loading图。
代理的意义
假如不使用虚拟代理来实现图片的预加载功能,代码如下:
class MyImage {
constructor() {
this.img = document.createElement('img');
document.body.appendChild(this.img);
this.image = new Image();
image.onload = function () {
this.img.src = this.image.src;
};
}
setSrc(src) {
this.img.src = './loading.gif';
this.image.src = src;
}
}
上段代码中,MyImage类中除了负责生成img节点并设置src,还要负责预加载图片。这样的写法违背了单一职责原则。
另外,假如当图片非常小时,根本不需要预加载,使用预加载还会出现占位图片一闪而过的问题,如果这个时候重新再写一个MyImage类或者去修改原来的代码,还会违背开放-封闭原则。
实际上MyImage类的功能只需要给img节点设置src,预加载图片只是一个锦上添花的功能,假如把这个功能放在代理中,那么代理的作用在这里就体现了,代理负责预加载图片,预加载的操作完成之后,把请求重新交给本体MyImage去设置src。
这里没有改变或增加MyImage功能,只是通过代理,给这个类进行了扩展。
给img节点设置src和图片预加载这两个功能,被隔离在了两个类中,它们各自变化而不影响对方。假如有一天不需要预加载,那么只需要请求改成本体而不是请求代理对象即可。
缓存代理
缓存代理可以为一些开销大的运算结果提供暂时的存储,在下次运算时,如果传递进来的参数和之前一致,则可以直接返回前面存储的运算结果。
使用缓存代理进行计算乘积
高阶函数可以简单理解为把一个函数作为参数传入另一个函数中。这里使用高阶函数动态创建缓存代理,就是实现一个专门用于创建缓存代理的工厂函数,把要代理的函数传入到这个工厂函数中,返回一个代理函数。
const createProxyFactory = (fn) =>{
let cache = {};
return function () {
const args = Array.prototype.join.call(arguments, ',');
if (args in cache) {
return cache[args];
}
return cache[args] = fn.apply(this, arguments);
}
};
实现一个mult函数
const mult = function () {
let a = 1;
for (let i = 0, l = arguments.length; i < l; i++) {
a = a * arguments[i];
}
return a;
};
let proxyPlus=createProxyFactory(mult)
proxyPlus(1,2,3,4)
proxyPlus(1,2,3,4) //第二次不用再计算,直接从缓存中获取
把mult函数当作参数传入createProxyFactory中,即可实现mult缓存代理。