JavaScript 设计模式:代理模式详解与虚拟代理实战

作者:闭包旅人

时间:2026-10-24

浏览:357

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

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

  • 意义:引入面向对象设计原则中的单一职责原则,即一个对象应尽可能少地承担职责,最好只有一个。如果职责过多,会增加代码耦合度,导致设计脆弱且内聚性低。当需求变化时,设计可能遭到破坏。虽然可以将代码封装到一个函数中,但引入代理模式是更优的处理方式。
  • 要求:代理对象与本体对象的接口必须保持一致,在任何可以使用本体的地方,都可以用代理对象进行替代。

六、总结

代理模式包含多个细分模块,其中最常用的是缓存代理和虚拟代理。尽管代理模式非常有用,但在编写业务代码时,无需预先猜测是否需要使用代理。当发现不方便直接访问某个对象,且真正需要控制访问时,再引入代理模式也不迟。

本文到此结束,希望能为读者带来帮助,也欢迎大家关注本站以获取更多技术内容。

实用工具推荐

电子包浆

绿化、压缩、噪点和虚拟水印,自由调节做旧程度。

使用 ›
图片压缩

选择画质和输出格式,预览压缩前后的大小。

使用 ›
相关文章