JavaScript 代理模式详解:从基础概念到虚拟代理实战

作者:异步漫步时间:2026-10-24 21:53:59浏览:117

本文深入解析 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);
                }
            }
        }

五、代理的使用意义及要求

使用意义:引入代理模式符合面向对象设计中的单一职责原则。一个对象应尽可能少地承担职责,理想情况下仅负责单一功能。若职责过多,会导致代码耦合度升高,进而引发脆弱性和低内聚性。当需求变更时,原有设计易受破坏。虽然可将功能封装至函数中,但引入代理模式能更优雅地处理访问控制与职责分离。

使用要求:代理对象与本体的接口必须保持一致,确保在任何可以使用本体的上下文中,均可无缝替换为代理对象。

六、总结

代理模式包含多种细分模块,其中缓存代理与虚拟代理最为常用。尽管代理模式功能强大,但在编写业务代码时,无需预先猜测是否必须使用代理。建议在开发过程中,当发现直接访问对象存在不便或性能瓶颈时,再适时引入代理模式进行优化。

本文内容至此结束,希望能为读者提供有价值的参考,欢迎持续关注本站获取更多技术分享!

作者最新文章