青训营 详解前端框架中的设计模式之代理模式 | 豆包MarsCode AI刷题

69 阅读4分钟

什么是代理模式?

代理模式(Proxy Pattern)是一种结构型设计模式,其核心思想是为其他对象提供一个代理,以控制对这个对象的访问。代理模式可以在不修改原始对象的前提下,通过代理对象来扩展、控制或优化对原始对象的访问。

代理模式的结构通常包含三个角色:

  1. Subject(抽象主题): 定义了真实对象和代理对象的共同接口,可以是接口或抽象类。
  2. RealSubject(真实主题): 真正执行业务逻辑的对象。
  3. Proxy(代理): 代理对象,持有对真实对象的引用,可以在调用真实对象方法前后进行额外的操作。

代理模式的实现原理

在 JavaScript 中,代理模式通常通过以下两种方式实现:

  1. 手动代理:通过自定义函数或类实现。
  2. ES6 的 Proxy 对象:使用 Proxy 构造函数创建代理。

以下只使用ES6 的 Proxy 对象进行示例

前端框架有哪些应用场景?

在前端开发中,代理模式应用广泛,以下是几个常见的使用场景:

  1. Vue3 的响应式系统:Vue3 使用 Proxy 实现数据响应式。
  2. 缓存优化:通过代理实现接口数据缓存。
  3. 图片懒加载: 使用虚拟代理模式,先使用一个占位图片,当图片进入可视区域时再加载真实的图片。

以下是代码示例:

1.Vue3 响应式系统中的代理模式

Vue3 的响应式数据系统是代理模式的经典实现。它通过 Proxy 拦截对数据对象的访问和修改,从而实现自动更新视图的功能。

以下是一个简化版的 Vue3 响应式系统示例:

// 简化版响应式实现
function reactive(target) {
  return new Proxy(target, {
    get(target, prop) {
      console.log(`读取属性: ${prop}`);
      return target[prop];
    },
    set(target, prop, value) {
      console.log(`修改属性: ${prop}, 新值: ${value}`);
      target[prop] = value;
      // 这里可以触发视图更新逻辑
      return true;
    }
  });
}

// 创建响应式对象
const state = reactive({
  count: 0,
  message: 'Hello'
});

// 使用响应式对象
console.log(state.count); // 输出: 读取属性: count 输出 0
state.count = 1; 
// 输出: 修改属性: count, 新值: 1

console.log(state.message); 
// 输出: 读取属性: message
// 输出: Hello

state.message = 'Hello, Proxy!';
// 输出: 修改属性: message, 新值: Hello, Proxy!

2.接口数据的缓存优化

在前端开发中,频繁调用相同的接口可能会浪费网络资源和影响性能。通过代理模式,我们可以实现接口数据的缓存,避免重复请求。

// 创建接口数据缓存代理
function createApiCache(apiFunction) {
  const cache = new Map();

  return new Proxy(apiFunction, {
    apply(target, thisArg, args) {
      const cacheKey = JSON.stringify(args);
      if (cache.has(cacheKey)) {
        console.log('从缓存中获取数据:', cacheKey);
        return cache.get(cacheKey);
      } else {
        console.log('发送新的请求:', cacheKey);
        const result = target.apply(thisArg, args);
        cache.set(cacheKey, result);
        return result;
      }
    }
  });
}

// 模拟接口请求
function fetchData(endpoint) {
  return `数据来自接口 ${endpoint}`;
}

// 创建带缓存的接口函数
const cachedFetchData = createApiCache(fetchData);

// 测试接口缓存
console.log(cachedFetchData('/api/users')); // 输出: 发送新的请求: ["/api/users"] 数据来自接口 /api/users
console.log(cachedFetchData('/api/users')); // 输出: 从缓存中获取数据: ["/api/users"] 数据来自接口 /api/users
console.log(cachedFetchData('/api/posts')); // 输出: 发送新的请求: ["/api/posts"] 数据来自接口 /api/posts
console.log(cachedFetchData('/api/posts')); // 输出: 从缓存中获取数据: ["/api/posts"] 数据来自接口 /api/posts

3.图片懒加载示例:

以下是一个简单的图片懒加载的例子,使用了虚拟代理模式:

// 抽象主题
interface Image {
  display(): void;
}

// 真实主题
class RealImage implements Image {
  private url: string;

  constructor(url: string) {
    this.url = url;
    this.load();
  }

  private load(): void {
    console.log(`Loading image from ${this.url}`);
  }

  display(): void {
    console.log(`Displaying image: ${this.url}`);
  }
}

// 虚拟代理
class LazyImageProxy implements Image {
  private realImage: RealImage | null = null;
  private url: string;

  constructor(url: string) {
    this.url = url;
  }

  display(): void {
    if (!this.realImage) {
      this.realImage = new RealImage(this.url);
    }
    this.realImage.display();
  }
}

// 使用示例
const image1 = new LazyImageProxy('image1.jpg');
const image2 = new LazyImageProxy('image2.jpg');

// 第一次调用 display 时才会加载并显示图片
image1.display();
image2.display();

// 第二次调用 display 时直接显示,不会重新加载
image1.display();

代理模式的优缺点

优点:

  • 职责分离: 代理模式可以将一些非核心逻辑(如访问控制、缓存、延迟加载等)从原始对象中分离出来,使代码更加清晰、易于维护。
  • 开放封闭原则: 可以在不修改原始对象的情况下,通过增加新的代理类来扩展功能,符合开放封闭原则。
  • 灵活性和可扩展性: 代理模式提供了很大的灵活性,可以根据需要选择不同类型的代理,并且可以方便地增加新的代理类。
  • 控制访问: 保护代理可以控制对原始对象的访问权限,提高安全性。
  • 性能优化: 缓存代理可以减少对原始对象的访问次数,提高性能。

缺点:

  • 增加复杂性: 引入代理模式会增加代码的复杂性,因为需要额外的代理类或代理对象。
  • 可能影响性能: 如果代理逻辑比较复杂,可能会对性能产生一定的影响。特别是在频繁访问的情况下,代理的额外开销可能会变得明显。
  • 调试困难: 由于引入了中间层,调试可能会变得更加困难,需要跟踪代理对象和原始对象之间的交互