为我们访问的对象提供一个中间对象(代理)以控制对这个对象的访问。
在某些情况下,一个对象不适合或者不能直接引用另一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用。就比如A 对象本来想要直接访问B对象,但是B对象觉得不太方便,所以找了个中介者C(代理),以后所有的都要通过C对象去访问B对象,关系如下图:
生活中也有很多代理模式的例子,就如上图中最下方,我们在租房市场中的关系,有些房东想要出租房子为了省心省事不想一个一个自己去和客户对接,所以找了一个中介(也就是代理),我们找房子的时候,都是需要通过中介然后中介在去联系房东,这种模式就叫做代理模式。根据代理的行为来说又大概分为保护代理、虚拟代理、缓存代理等。
把一些开销很大的对象,延迟到真正需要它的时候才去创建。
在实际业务中有时候直接加载较大图片时可能会造成页面空白,所以为了优化体验采用图片预加载的方式来优化(不知道图片预加载原理的可以看下这篇没有使用代理实现图片预加载的文章js实现图片预加载,下面我们就来看看虚拟代理在图片预加载技术中的应用,代码如下:
const imgSet = (() => { // 目标对象
const imgNode = document.createElement('img');
document.body.appendChild(imgNode);
return (url) => imgNode.src = url;
})();
const proxyImgSet = (() => { // 代理对象
const img = new Image();
img.onload = () => imgSet(img.src); // 使目标节点加载已经缓存好的图片
return (url) => {
// 加载目标对象前,代理增加一些自己的行为
imgSet('./images/ef9081ecc65482ed7bcfc9b7e0b548d6.gif');
img.src = url;
}
})();
proxyImgSet('https://img1.baidu.com/it/u=3615107494,579574018&fm=253&fmt=auto&app=138&f=JPEG?w=667&h=500');
上述代码没有对目标对象直接加载图片,因为此时对它来说加载较大的图片开销较大,所以通过代理先设置一张小的占位图,同时进行异步缓存较大的图片,等图片缓存完成的时候代理对象在通知目标对象直接加载缓存里面的图片。代理在中间增加了自己的行为,使体验更友好。
为一些开销大的运算结果提供暂时的存储,在下次运算时,如果传递进来的参数跟之前一致,则可以直接返回前面存储的运算结果。
有些业务场景下会有比较复杂的数据处理或者计算,如果传入的参数都相同再次计算就会浪费性能,所以我们就把之前的计算做一个缓存,如果传入的参数相同,就之前返回上一次计算的值以节省开销。类似的思想在很多库中都有使用,如reselect库和vue中的computed计算属性中。下面看下使用代理实现为计算规则创建缓存的实例:
// 乘积计算处理
const mult = (...list) => {
console.log('我乘积计算处理')
let num = 1;
list.forEach(item => num = num * item)
return num;
}
// 和计算处理
const add = (...list) => {
console.log('我是和计算处理');
let num = 0;
list.forEach(item => num += item);
return num;
}
// 创建缓存的代理
const createProxy = (fn) => {
// 闭包形成一个缓存对象
const cacheObj = {};
return function(...list) {
const args = list.join('-');
if (cacheObj[args]) return cacheObj[args];
return cacheObj[args] = fn.apply(this, list);
}
}
// 使用代理创建函数
const proxyMult = createProxy( mult ),
proxyPlus = createProxy( add );
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
上述代码首先定义了两个计算规则mult 和add ,然后创建createProxy代理函数来调用计算函数,代理函数内部是会用闭包来缓存每次计算的结果,当结果相同时就不会在重复计算由代理函数负责直接返回之前的结果,计算函数只会执行一次。并且如果我们不想使用代理也可以直接调用原来的计算函数,不用做其他任何的修改。下面看下控制台上的打印结果:
在 JavaScript 开发中最常用的是虚拟代理和缓存代理。这些业务不适用代理模式思想一样可以进行实现,但是使用代理也有更多的优点: