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调用代理和数据库访问控制。

相关推荐
芭拉拉小魔仙7 小时前
Vue项目中如何实现表格选中数据的 Excel 导出
前端·vue.js·excel
Arva .7 小时前
开发准备之日志 git
spring boot·git·后端
jump_jump7 小时前
妙用 localeCompare 获取汉字拼音首字母
前端·javascript·浏览器
小宁爱Python7 小时前
从零搭建 RAG 智能问答系统1:基于 LlamaIndex 与 Chainlit实现最简单的聊天助手
人工智能·后端·python
U.2 SSD8 小时前
Echarts单轴坐标系散点图
前端·javascript·echarts
苏三说技术8 小时前
高性能场景为什么推荐使用PostgreSQL,而非MySQL?
后端
slim~8 小时前
CLion实现ini 解析器设计与实现
c++·后端·clion
德育处主任Pro8 小时前
前端玩转大模型,DeepSeek-R1 蒸馏 Llama 模型的 Bedrock 部署
前端·llama
程序员飞哥8 小时前
如何设计多级缓存架构并解决一致性问题?
java·后端·面试
Jedi Hongbin8 小时前
Three.js NodeMaterial 节点材质系统文档
前端·javascript·three.js·nodematerial