本文深入解析 JavaScript 中的代理模式,通过送花案例直观展示其核心思想。重点介绍保护代理与虚拟代理的区别,并详细阐述虚拟代理在图片预加载、缓存代理及合并 HTTP 请求中的实际应用,帮助开发者理解如何通过代理控制对象访问并优化性能。
一、初识代理模式
代理模式的核心在于为某个对象提供一个替代对象或占位符,从而实现对原对象访问的控制。当客户端无法直接访问目标对象,或者访问条件不满足时,代理对象便作为中间层介入,负责处理访问请求。简单来说,代理就像是一个中间人,它在客户端与目标对象之间传递信息,过滤无效请求,并优化传递给目标对象的信息,从而提高交互的成功率。
二、代理模式的实现思想
接下来,我们将通过一个“小明给女神送花”的具体案例来演示代理模式的实现过程。
首先展示未使用代理模式的情况。
let Flower = function () {};
let xiaomingFirst = {
sendFlower ( target ) {
let flower = new Flower();
target.receiveFlower( flower );
}
}
let A = {
receiveFlower ( flower ) {
console.log('收到花')
}
}
xiaomingFirst.sendFlower(A);随后,我们将使用代理模式对代码进行重构。
设定具体需求:假设 AA 是小明的女神,她在心情好时接受小明送花的概率更高。B 是小明和 AA 共同的好友,因此小明将花送给 B,委托 B 在 AA 心情好时转达自己的心意。
代码分析:重构后的代码引入了一个新的对象 B,此时 B 充当了 AA 的代理。B 负责监听 AA 的心情状态,代码中假设 5 秒后 AA 心情变好,B 便会执行送花操作。
let xiaomingSencend = {
sendFlower ( target ) {
let flower = new Flower();
target.receiveFlower( flower );
}
}
let AA = {
receiveFlower () {
console.log('收到花');
},
listenGoodMood ( fn ) {
setTimeout(()=>{
fn();
}, 5000);
}
}
let B = {
receiveFlower ( flower ) {
AA.listenGoodMood( ()=>{
AA.receiveFlower( flower );
} );
}
}
xiaomingSencend.sendFlower(B);三、代理模式的分类
上述代码中的代理模式看似简单,但其体现的思想至关重要。如果女神 AA 对送花男生有特定要求(如必须帅气且富有),代理可以帮助过滤掉不符合条件的追求者,既减少了女神的麻烦,又维护了她的形象。此外,如果送花成本高昂,不仅仅是简单的 `new` 操作,男生希望先确认女神是否接受,再决定是否购买,代理可以代为询问。如果女神接受,代理再执行购买和赠送,这样既减少了男生的损失,又达成了目的。基于此,引出两种常见的代理模式。
- 保护代理:在本体中直接定义访问要求,由代理负责过滤掉不符合要求的访问者对本体的请求。
- 虚拟代理:将一些开销较大的操作推迟到真正需要向本体发起请求时才执行,这在实际开发中应用广泛。
四、虚拟代理模式的实际运用
1. 虚拟代理实现图片预加载
实现逻辑:首先加载本地占位图片,随后发起网络请求获取真实图片。当图片加载成功后,再通过 `myImage` 对象将占位图片替换为真实图片。
let myImage = function () {
let img = document.createElement('img');
document.body.appendChild(img);
return {
setSrc ( src ) {
img.src = src;
}
}
}();
proxyImage = function () {
let img = new Image;
img.onload = function () {
myImage.setSrc( this.src );
}
return {
setProxyImage( src ) {
myImage.setSrc("https://img.zcool.cn/community/01e2115d5d5c7da80120695c137bfb.jpg@1280w_1l_2o_100sh.jpg"); // 此处为加载 loading 图片,用来占位,本地图片(作者使用网络图片)
img.src = src;
}
}
}();
proxyImage.setProxyImage("https://img.zcool.cn/community/01a5d45543cd170000019ae94fc087.jpg@1280w_1l_2o_100sh.jpg");2. 缓存代理
实现逻辑:在代理中缓存本体计算的结果。如果后续遇到相同的请求,直接从缓存中读取结果,从而减少对本体的重复访问,降低性能消耗。
let myImage = function () {
let img = document.createElement('img');
document.body.appendChild(img);
return {
setSrc ( src ) {
img.src = src;
}
}
}();
proxyImage = function () {
let img = new Image;
img.onload = function () {
myImage.setSrc( this.src );
}
return {
setProxyImage( src ) {
myImage.setSrc("https://img.zcool.cn/community/01e2115d5d5c7da80120695c137bfb.jpg@1280w_1l_2o_100sh.jpg"); // 此处为加载 loading 图片,用来占位,本地图片(作者使用网络图片)
img.src = src;
}
}
}();
proxyImage.setProxyImage("https://img.zcool.cn/community/01a5d45543cd170000019ae94fc087.jpg@1280w_1l_2o_100sh.jpg");3. 虚拟代理合并 HTTP 请求
应用场景:当用户点击复选框向服务器发起同步文件请求时,每次点击都会产生一次网络请求,导致巨大的网络开销。优化方案是将需要同步的文件缓存起来,在 3 秒后通过一次请求批量发送到服务器。
<input type="checkbox" >1
<input type="checkbox" >2
<input type="checkbox" >3
<input type="checkbox" >4
<input type="checkbox" >5
<input type="checkbox" >6
<input type="checkbox" >7
<input type="checkbox" >8
<script>
// 同步文件
let synchronousFile = function ( id ) {
console.log("开始同步文件" + id);
}
// 代理实现同步文件
let proxySynchronousFile = function ( ) {
let cache = [];
let time;
return function ( id ) {
cache.push( id );
if( time ) {
return;
}
time = setInterval(() => {
synchronousFile(cache.join(','));
clearInterval(time);
time = null;
cache.length = 0;
}, 3000);
}
}()
// 添加点击执行
let checkboxList = document.getElementsByTagName('input');
for(let i = 0, l = checkboxList.length; i < l; i++) {
checkboxList[i].onclick = function() {
if( this.checked === true ) {
proxySynchronousFile(i+1);
}
}
}五、代理的使用意义及要求
- 意义:引入面向对象设计原则中的单一职责原则,即一个对象应尽可能少地承担职责,最好只有一个。如果职责过多,会增加代码耦合度,导致设计脆弱且内聚性低。当需求变化时,设计可能遭到破坏。虽然可以将代码封装到一个函数中,但引入代理模式是更优的处理方式。
- 要求:代理对象与本体对象的接口必须保持一致,在任何可以使用本体的地方,都可以用代理对象进行替代。
六、总结
代理模式包含多个细分模块,其中最常用的是缓存代理和虚拟代理。尽管代理模式非常有用,但在编写业务代码时,无需预先猜测是否需要使用代理。当发现不方便直接访问某个对象,且真正需要控制访问时,再引入代理模式也不迟。
本文到此结束,希望能为读者带来帮助,也欢迎大家关注本站以获取更多技术内容。