这是我参与8月更文挑战的第4天,活动详情查看:8月更文挑战
代理模式的定义
使用代理模式,在访问一个对象时,我们需要提供一个代用品(或占用符),以便控制对这个对象的访问。这个代用品我们称之为代理。
代理模式是一种非常有意义的模式,它的关键是当客户不方便直接访问一个对象时,提供一个替身对象来控制对这个对象的访问,客户实际上访问的是替身对象。替身对象会对客户的请求作出一些处理之后,再把请求转交给本体对象。
代理模式细分起来可以分为保护代理、虚拟代理、防火墙代理、远程代理、智能引用代理、写时复制代理、缓存代理。
引用自《JavaScript设计模式与开发实践》,关于各个应用种类的定义
- 保护代理:用于对象应该有不同访问权限的情况
- 虚拟代理:把一些开销很大的对象,延迟到真正需要它的时候再执行
- 防火墙代理:控制网络资源的访问,保护主题不让“坏人”接近
- 远程代理:为一个对象在不同的地址空间提供局部代表,在 Java 中,远程代理可以是另一个虚拟机中的对象
- 智能引用代理:取代了简单的指针,它在访问对象时执行一些附加操作,比如计算一个对象被引用的次数
- 写时复制代理:通常用于复制一个庞大对象的情况。写时复制代理延迟了复制的过程,当对象被真正修改时,才对它进行复制操作。写时复制代理是虚拟代理的一种变体,DLL(操作系统中的动态链接库)是其典型运用场景
- 缓存代理:可以为一些开销大的运算结果提供暂时的存储,在下次运算时,如果传递进来的参数和之前一致,则可以直接返回前面存储的结果
虽然代理模式包括许多小分类,但是在 JavaScript 开发中最常用的是虚拟代理和缓存代理。本文也将着重就这两种代理的类型来做阐述。
代理模式的优缺点
优点:
- 节省代码执行开支,如虚拟代理、保护代理,都有效的减少目标函数的调用次数
- 代码层次分明,增删简单,方便维护 缺点:
- 代码编写量多
- 对编码者的能力有一定要求
代理模式的应用场景
虚拟代理
虚拟代理会把一些开销很大的对象,延迟到真正需要它的时候再执行。我们可以做一个场景假设,单身狗A想要送礼物给女神C表白,但是单身狗拿捏不住女神的心思,不知道选什么礼物在什么时机表白成功率最高,好在他们有个共同的朋友B,A把买礼物和送礼物这个操作全权委托B去操作,自己在适当的时机出现,结果完美。
var B = {
sendGift: function() {
C.listenGoodMood(function() { // 时刻监听女神C的好心情
var gift = new Gift(); // 到了适当的时机再买礼物
C.receiveGift(gift); // 女神C顺利接收礼物
});
}
}
复制代码
上述是一个助于我们理解虚拟代理的场景解释。在实际业务开发中,图片预加载是最常用的场景之一。图片加载时,如果直接给某个 img 标签节点设置 src 属性,由于图片过大或者网络不佳,图片的位置往往会有段空白期。常见的做法是先用 loading 图片占位,然后用异步的方式加载图片,等图片加载好了再把它填充到 img 节点里,这就是图片预加载。
缓存代理
接下来看看缓存代理,它的作用更多的是体现在缓存两个字上,通过设置暂时存储空间,存储数据,减少重复的计算逻辑和接口请求等。
它的应用场景有用于处理计算的函数和 ajax 异步请求数据如分页请求。
代理模式的实现原理分析
图片预加载(虚拟代理)
- 新建一个对象 myImage,用来负责往页面中创建 img 标签,提供一个对外的 setSrc 接口,外界通过这个接口给 img 标签设置 src 属性
- 引入代理对象 proxyImage,通过代理对象,在图片加载好之前,img 先被设置成占位的 loading.gif 提示用户图片正在加载
- 代理对象暴露出 setSrc 方法,控制用户对 MyImage 的访问,并且在此过程中加入一些额外操作
- 用户直接调用代理对象提供的 setSrc 方法,传入目标图片
ajax 异步请求数据(缓存代理)
- 实现核心接口请求函数 handle
- 加入创建缓存代理的工厂函数 createProxyHandle
- 调用工厂函数新建处理接口的代理函数 proxy
- 调用代理函数进行请求
具体实现的代码
虚拟代理实现图片预加载
// 新建用于创建 img 的对象
var myImage = (function() {
var imgNode = document.createElement('img');
document.body.appendChild( imgNode );
return {
setSrc: function(src) {
imgNode.src = src;
}
}
})();
// 新建代理对象,用于控制对 myImage 的访问
var proxyImage = (function() {
var img = new Image;
// 图片加载完成后执行
img.onload = function() {
myImage.setSrc(this.src);
}
return {
setSrc: function(src) {
// 默认先设置成 loading
myImage.setSrc('loading.gif');
// 开始对目标图片进行加载
img.src = src;
}
}
})();
// 用户调用时传入目标图片 aim.png
proxyImage.setSrc('aim.png');
复制代码
缓存代理处理 ajax 异步请求数据
// 核心处理函数
var handle = function(api) {
// 用延时模拟接口请求异步返回
setTimeout(function(){
console.log('接口返回:', api);
}, 1000);
};
// 用于批量生成代理的工厂函数
var createProxyHandle = function(fn) {
var cache = {};
return function(api) {
if(api in cache) {
console.log(api+'重复,直接返回');
return cache[api];
}
return cache[api] = fn.apply(this, [api]);
}
};
// 利用工厂函数生成缓存代理
var proxy = createProxyHandle(handle);
// 尝试一下
proxy('请求1');
proxy('请求1');
proxy('请求2');
复制代码