TypeScript设计模式:代理模式

代理模式(Proxy Pattern)是一种结构型设计模式,通过为另一个对象(主体)提供一个代理或占位符来控制对其的访问。代理对象可以在调用主体前后添加额外逻辑,如访问控制、数据缓存或日志记录,而客户端无需直接与主体交互,保持透明性。

设计模式原理

代理模式的核心是通过代理对象控制对主体对象的访问,代理与主体实现相同的接口(或直接拦截操作),客户端通过代理间接访问主体。代理可以在调用前后添加逻辑,如数据验证、通知或缓存。在Vue.js中,ES6 Proxy拦截对象的 getter 和 setter,跟踪依赖并触发更新。

结构

  • 主题接口(Subject):定义代理和主体的公共接口(在ES6 Proxy中,主体是普通对象,接口隐式)。
  • 真实主题(RealSubject):实际数据对象(如Vue.js中的data对象)。
  • 代理(Proxy):拦截对真实主题的访问,添加响应式逻辑。
  • 客户端(Client):通过代理访问数据,如Vue组件。

优点

  • 访问控制:代理可以拦截和验证操作,增强数据安全性。
  • 功能增强:支持添加响应式、日志或缓存逻辑。
  • 透明性:客户端无需感知代理,直接操作数据。
  • 动态性:ES6 Proxy提供灵活的拦截机制,适合动态场景。

缺点

  • 复杂性增加:代理逻辑可能增加代码复杂度。
  • 性能开销:频繁拦截可能带来轻微性能影响。
  • 浏览器兼容性:ES6 Proxy不支持旧浏览器(如IE)。
  • 调试难度:代理拦截可能使错误追踪更复杂。

适用场景

  • 响应式系统:如Vue.js的数据绑定、状态管理。
  • 访问控制:限制对敏感数据的操作。
  • 日志/监控:记录数据访问和修改日志。
  • 延迟加载:按需加载数据或资源。

TypeScript 实现示例

我们实现一个简化的Vue.js响应式系统,使用ES6 Proxy模拟Vue 3的响应式原理。代理拦截数据对象的 getter 和 setter,跟踪依赖并在数据变化时通知视图更新。

项目结构

go 复制代码
vue-reactive-proxy/
├── src/
│   ├── reactive.ts      // 响应式代理实现
│   ├── component.ts     // 客户端(Vue组件)
│   ├── main.ts         // 执行示例
├── tsconfig.json
├── package.json

1. 安装依赖

bash 复制代码
npm init -y
npm install typescript @types/node
npx tsc --init

配置 tsconfig.json

json 复制代码
{
  "compilerOptions": {
    "target": "ES2020",
    "module": "ESNext",
    "strict": true,
    "moduleResolution": "node"
  }
}

2. 实现响应式代理 (reactive.ts)

typescript 复制代码
// 依赖类,用于跟踪和通知
class Dep {
  private subscribers: Set<() => void> = new Set();

  depend(fn: () => void): void {
    this.subscribers.add(fn);
    console.log('依赖已注册');
  }

  notify(): void {
    this.subscribers.forEach(fn => {
      fn();
      console.log('通知依赖更新');
    });
  }
}

// 响应式代理
export function reactive<T extends object>(target: T): T {
  const deps: Map<string | symbol, Dep> = new Map();

  return new Proxy(target, {
    get(target: T, key: string | symbol, receiver: any): any {
      console.log(`代理:读取属性 ${String(key)}`);
      const dep = deps.get(key) || new Dep();
      deps.set(key, dep);

      // 模拟Vue的依赖收集
      if (typeof window !== 'undefined' && window['activeEffect']) {
        dep.depend(window['activeEffect']);
      }

      return Reflect.get(target, key, receiver);
    },

    set(target: T, key: string | symbol, value: any, receiver: any): boolean {
      console.log(`代理:设置属性 ${String(key)} = ${value}`);
      const result = Reflect.set(target, key, value, receiver);

      // 触发依赖更新
      const dep = deps.get(key);
      if (dep) {
        dep.notify();
      }

      return result;
    }
  });
}

说明

  • Dep类管理依赖(视图更新函数),depend注册依赖,notify触发更新。
  • reactive函数创建ES6 Proxy,拦截getset操作:
    • get:记录访问的属性并收集依赖(模拟Vue的effect)。
    • set:更新属性并通知依赖更新。
  • window['activeEffect']模拟Vue的effect上下文,用于收集当前运行的视图更新函数。

3. 实现客户端 (component.ts)

typescript 复制代码
import { reactive } from './reactive';

interface Data {
  count: number;
  message: string;
}

export class VueComponent {
  private data: Data;

  constructor() {
    // 创建响应式数据
    this.data = reactive({
      count: 0,
      message: '欢迎使用Vue'
    });
  }

  // 模拟视图渲染(依赖)
  render(): void {
    // 模拟Vue的effect,设置当前依赖
    (window as any).activeEffect = () => this.updateView();
    console.log('开始渲染视图');
    // 访问数据,触发get拦截
    const count = this.data.count;
    const message = this.data.message;
    console.log(`视图渲染:count=${count}, message="${message}"`);
    (window as any).activeEffect = null; // 清理effect
  }

  // 模拟视图更新
  private updateView(): void {
    console.log('视图更新:重新渲染');
    console.log(`当前数据:count=${this.data.count}, message="${this.data.message}"`);
  }

  // 修改数据,触发set拦截
  updateData(key: keyof Data, value: any): void {
    console.log(`Vue组件:更新数据 ${key}=${value}`);
    this.data[key] = value;
  }

  getData(): Data {
    return this.data;
  }
}

说明

  • VueComponent模拟Vue组件,持有响应式数据(通过reactive创建)。
  • render模拟视图渲染,访问数据触发get拦截,注册依赖。
  • updateData修改数据,触发set拦截,自动调用updateView

4. 运行示例 (main.ts)

typescript 复制代码
import { VueComponent } from './component';

// 创建Vue组件
const component = new VueComponent();

// 首次渲染(收集依赖)
component.render();

// 更新数据(触发响应式更新)
component.updateData('count', 1);
component.updateData('message', '已更新');

// 再次访问数据(不触发渲染,仅读取)
console.log(`直接访问数据:count=${component.getData().count}, message="${component.getData().message}"`);

5. 编译与运行

bash 复制代码
npx tsc
node dist/main.js

运行后,控制台输出类似:

ini 复制代码
开始渲染视图
代理:读取属性 count
依赖已注册
代理:读取属性 message
依赖已注册
视图渲染:count=0, message="欢迎使用Vue"
Vue组件:更新数据 count=1
代理:设置属性 count=1
通知依赖更新
视图更新:重新渲染
当前数据:count=1, message="欢迎使用Vue"
Vue组件:更新数据 message=已更新
代理:设置属性 message=已更新
通知依赖更新
视图更新:重新渲染
当前数据:count=1, message="已更新"
直接访问数据:count=1, message="已更新"
代理:读取属性 count
代理:读取属性 message

解释输出

  • render触发get拦截,收集依赖(updateView)。
  • 修改countmessage触发set拦截,通知依赖,自动调用updateView更新视图。
  • 直接访问数据只触发get,不触发渲染,模拟Vue的响应式机制。
  • 代理透明地拦截数据操作,客户端(VueComponent)无需感知代理逻辑。

总结

代理模式的优点在于其访问控制、功能增强、透明性和动态性。代理可以拦截操作,增强数据安全性;支持添加响应式、日志或缓存逻辑;客户端直接操作数据,无需感知代理;ES6 Proxy提供灵活的拦截机制。该模式特别适用于响应式系统、访问控制、日志/监控和延迟加载场景,如Vue.js的响应式数据绑定、状态管理、API调用代理和数据库访问控制。

相关推荐
猪八戒1.029 分钟前
onenet接口
开发语言·前端·javascript·嵌入式硬件
程序猿小蒜32 分钟前
基于Spring Boot的宠物领养系统的设计与实现
java·前端·spring boot·后端·spring·宠物
合作小小程序员小小店32 分钟前
web网页开发,在线%食堂管理%系统,基于Idea,html,css,jQuery,java,ssm,mysql。
java·前端·mysql·html·intellij-idea·jquery
人工智能训练1 小时前
Windows中如何将Docker安装在E盘并将Docker的镜像和容器存储在E盘的安装目录下
linux·运维·前端·人工智能·windows·docker·容器
90后小陈老师1 小时前
用户管理系统 07 项目前端初始化 | 新手实战 | 期末实训 | Java+SpringBoot+Vue
java·前端·spring boot
k***82511 小时前
springboot整合libreoffice(两种方式,使用本地和远程的libreoffice);docker中同时部署应用和libreoffice
spring boot·后端·docker
tan180°1 小时前
Linux网络TCP(上)(11)
linux·网络·c++·后端·tcp/ip
小溪彼岸1 小时前
一键切换Cluade、Codex供应商配置,CC Switch你值得一试
前端
2501_916008891 小时前
API接口调试全攻略 Fiddler抓包工具、HTTPS配置与代理设置实战指南
前端·ios·小程序·https·fiddler·uni-app·webview
l***46681 小时前
springboot使用redis
spring boot·redis·后端