JavaScript 代理模式详解:从基础概念到虚拟代理实战
本文深入解析 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 的代理负责监听 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);三、代理模式的分类
上述示例中的代理模式看似简单,却蕴含了代理设计的核心思想。若女神对送花者有“必须帅气且富有”的隐性要求,代理可以帮助过滤掉不符合条件的追求者,既减少了女神处理无效请求的麻烦,又维护了其形象。此外,鲜花成本高昂,并非简单的对象实例化,小明希望先确认女神是否接受,再决定是否购买。代理在此处代为询问,若获准则执行购买,从而降低男生的经济损失并达成目的。基于此背景,衍生出两种常见的代理类型。
- 保护代理:在代理层直接实现访问控制逻辑,过滤掉不符合本体要求的请求,阻止其直接访问本体。
- 虚拟代理:将高开销的操作延迟到真正需要访问本体时才执行,这种模式在实际开发中应用广泛。
四、虚拟代理模式的实际运用
1. 虚拟代理实现图片预加载
逻辑分析:系统首先加载本地占位图片,随后异步发起网络请求获取真实图片。当图片资源加载成功后,代理对象调用本体方法,将占位图片替换为真实图片,从而优化用户视觉体验。
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 mult = function (...arg) {
console.log('我执行了')
let m = 1;
for(let i = 0,l = arg.length; i <l; i++){
m *= arg[i];
}
return m;
}
// 缓存代理
let proxyMult = function( fn ) {
let cache = {};
return function (...arg) {
let argS = arg.join(',');
if( cache[argS] ) {
return cache[argS];
}
return cache[argS] = fn.apply( this, arg);
}
};
let proxymult = proxyMult(mult);
console.log(proxymult(1,2,4))3. 虚拟代理合并 HTTP 请求
应用场景:在复选框交互场景中,每次点击复选框均向服务器发起同步文件请求,导致巨大的网络开销。优化方案为:将待同步的文件 ID 缓存至代理中,并在 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);
}
}
}五、代理的使用意义及要求
使用意义:引入代理模式符合面向对象设计中的单一职责原则。一个对象应尽可能少地承担职责,理想情况下仅负责单一功能。若职责过多,会导致代码耦合度升高,进而引发脆弱性和低内聚性。当需求变更时,原有设计易受破坏。虽然可将功能封装至函数中,但引入代理模式能更优雅地处理访问控制与职责分离。
使用要求:代理对象与本体的接口必须保持一致,确保在任何可以使用本体的上下文中,均可无缝替换为代理对象。
六、总结
代理模式包含多种细分模块,其中缓存代理与虚拟代理最为常用。尽管代理模式功能强大,但在编写业务代码时,无需预先猜测是否必须使用代理。建议在开发过程中,当发现直接访问对象存在不便或性能瓶颈时,再适时引入代理模式进行优化。
本文内容至此结束,希望能为读者提供有价值的参考,欢迎持续关注本站获取更多技术分享!
D3.js 比例尺与坐标轴的配置详解
2026-11-05 16:54:46
JavaScript怎么遍历对象的Symbol键和不可枚举属性
2026-11-05 13:13:39
封装一个健壮的JS字符串判空工具函数
2026-11-05 12:16:14
JavaScript合并数组的三种常用方法:concat、扩展运算符与push
2026-11-05 11:22:29
JavaScript立即执行函数(IIFE)是什么及基本语法
2026-11-05 10:51:19
