JavaScript设计模式(三):代理模式实现与应用

1、代理模式定义

代理模式是为一个对象提供一个替身或占位符,以便控制对它的访问

典型的一个例子就是代购,你想买国外的限量版商品,但自己没有渠道或无法出国,所以找代购帮你去买。

2、核心思想

  1. 间接性 (Indirection)
    • 没有代理:直接访问,client → realObject
    • 有代理:client → proxy → realObjectproxy 层可以做很多事情。
  2. 分离关注点 (Separation of Concerns) :将核心业务逻辑和辅助功能分离。核心业务逻辑还是在真实对象中,代理层 Proxy 可以添加一些辅助功能,如收集日志、添加缓存等。
  3. 开闭原则 (Open-Closed Principle) :代理层 Proxy 不修改真实对象中原有代码,只是通过扩展来增强功能。

代理模式就是在访问者和目标对象之间建立一个中间层,这个中间层可以:

  • ✅ 控制访问时机(延迟加载)。
  • ✅ 控制访问权限(权限校验)。
  • ✅ 控制访问方式(缓存、日志)。
  • ✅ 隐藏复杂性(远程调用) 。
  • ✅ 增强功能而不修改目标对象 。

拿一个形象点的例子来说就是,想找明星的人必须先通过经纪人,而经纪人可以:

  • 过滤掉不合适的邀约(权限控制)。
  • 安排日程(延迟访问)。
  • 谈价格(添加功能)。
  • 处理合同(隐藏复杂性)。

3、代理模式的应用

3.1 虚拟代理 - 延迟加载

当操作代价比较昂贵时,我们可以将实际操作延迟到真正需要它的时候,这叫做虚拟代理,这样能降低执行开销。

js 复制代码
// 推迟昂贵操作,直到真正需要
class VirtualProxy {
    constructor(factory) {
        this.factory = factory;
        this.instance = null;
    }
    
    getInstance() {
        if (!this.instance) {
            this.instance = this.factory.create(); // 延迟创建
        }
        return this.instance;
    }
}

3.2 保护代理 - 权限控制

代理在中间可以过滤掉一些操作,这个叫做保护代理。我们可以根据用户角色,进行操作限制。

js 复制代码
// 根据访问者权限决定是否允许操作
class ProtectionProxy {
    constructor(realObject, allowedUsers) {
        this.realObject = realObject;
        this.allowedUsers = allowedUsers;
    }
    
    operation(user) {
        if (!this.allowedUsers.includes(user)) {
            throw new Error("Access denied");
        }
        return this.realObject.operation();
    }
}

3.3 缓存代理 - 结果复用

代理在中间也可以充当缓存作用,这就是缓存代理,可以缓存计算结果、接口请求结果等,大幅提高性能。

js 复制代码
// 缓存重复计算的结果
class CacheProxy {
    constructor(realObject) {
        this.realObject = realObject;
        this.cache = new Map();
    }
    
    operation(key) {
        if (this.cache.has(key)) {
            return this.cache.get(key); // 直接返回缓存
        }
        const result = this.realObject.operation(key);
        this.cache.set(key, result);
        return result;
    }
}

3.4 日志代理 - 监控追踪

通过代理,我们可以在具体的操作前后收集相关操作信息,比如操作时间、操作耗时、环境信息、用户标识等,并打印日志或者上报到日志服务器,这就是日志代理

js 复制代码
// 思想:在不修改业务代码的情况下添加监控
class LoggingProxy {
    constructor(realObject) {
        this.realObject = realObject;
    }
    
    operation(...args) {
        console.log(`[${new Date().toISOString()}] 调用 operation`, args);
        const start = performance.now();
        
        const result = this.realObject.operation(...args);
        
        const duration = performance.now() - start;
        console.log(`[${new Date().toISOString()}] 完成,耗时 ${duration}ms`);
        
        return result;
    }
}

3.5 远程代理 - 隐藏网络通信

针对一些远程服务,代理可以屏蔽和远程通信细节,让用户像本地对象一样使用远程服务,这叫做远程代理

js 复制代码
// 让远程服务像本地对象一样使用
class RemoteProxy {
    constructor(remoteUrl) {
        this.remoteUrl = remoteUrl;
    }
    
    async operation(data) {
        // 隐藏网络通信细节
        const response = await fetch(this.remoteUrl, {
            method: 'POST',
            body: JSON.stringify(data)
        });
        return response.json();
    }
}

// 客户端使用起来像本地对象
const proxy = new RemoteProxy('https://api.example.com');
const result = await proxy.operation({ cmd: 'query' });

4、代理模式的优缺点

4.1 优点:

  • 职责清晰,符合单一职责原则:代理模式将核心业务逻辑与辅助功能分离,代理层实现辅助功能,核心业务逻辑由真实对象来完成,职责分明。
  • 高扩展性,符合开闭原则:在不修改原有代码的情况下,通过代理层增加新功能。
  • 智能控制访问:可以在访问真实对象前后添加各种控制逻辑,比如利用保护代理增加权限控制。
  • 延迟加载,优化性能:可以在真正需要时才创建昂贵的对象,也就是之前说的虚拟代理。

4.2 缺点:

  • 增加系统复杂度:因为多加了一层代理层,复杂度自然就上去了,包括实现、维护、调试的复杂度,比如利用缓存代理增加了缓存层,在数据库更新数据的时候,需要主动让缓存失效,这无疑增加了复杂度。
  • 增加调用链路:代理增加了方法的调用层级,可能会降低性能。
  • 客户端和真实对象需要相同接口。比如真实对象有多个方法,缓存层也必须全部实现,即使它只代理了一个方法。
js 复制代码
// 真实对象有多个方法
class ComplexService {
    methodA() { }
    methodB() { }
    methodC() { }
    methodD() { }
}

// 代理必须实现所有方法,即使只需要代理部分方法
class ProxyService {
    constructor(service) {
        this.service = service;
    }
    
    methodA() {  // 即使不需要代理,也要实现
        return this.service.methodA();
    }
    
    methodB() {  // 即使不需要代理,也要实现
        return this.service.methodB();
    }
    
    methodC() {
        // 只关心 methodC 的缓存
        if (!this.cache) {
            this.cache = this.service.methodC();
        }
        return this.cache;
    }
    
    methodD() {  // 即使不需要代理,也要实现
        return this.service.methodD();
    }
}

小结

上面介绍了Javascript最经典的设计模式之一代理模式,代理模式就是为一个对象提供一个替身或占位符,以便控制对它的访问,它增加了一层代理层,我们可以很方便做权限控制、延迟加载、监控追踪等很多事情,同时代理模式也会增加系统复杂度、增加调用链路,实际项目中可根据需要使用。

往期回顾

相关推荐
mldong7 小时前
你的 Vue3 项目也能有钉钉同款审批流设计器:npm 装包,10 分钟画出第一条审批流
前端·vue.js
2分钟速写快排8 小时前
什么是 RAG?如何用 RAG 实现一个用户记忆?
前端·后端·ai编程
passerby60618 小时前
如何自己造一个时间处理库
前端·javascript·github
走到天涯海角10 小时前
react里面的长列表渲染优化
前端·react.js·前端框架
小羊没烦恼!10 小时前
Hello Web API系列教程——Web API与国际化
java·服务器·前端·javascript·php
北岛贰10 小时前
迷茫焦虑期,我做了一个带支付带官网的 AI 聊天虚拟恋人 App
前端·人工智能·后端
mayaairi11 小时前
Vue2 组件通讯(三):全局事件总线、PubSub、插槽与组件实例属性
前端·javascript·vue.js
kyriewen12 小时前
面试官问我:AI 都能写代码了,前端凭什么还值 25K
前端·javascript·人工智能
风骏时光牛马13 小时前
AI源码分析:拆解模型底层实现逻辑
前端
BillKu13 小时前
全局样式变量:CSS 自定义属性(--border-color)和SCSS 变量($border-color)的说明
javascript·css·scss