代理模式(Proxy Pattern)是一种结构型设计模式,它通过引入一个 “代理对象” 来控制对 “目标对象” 的访问。代理对象可以在不改变目标对象核心功能的前提下,增加额外的逻辑(如权限校验、缓存、日志记录、延迟加载等),从而实现对目标对象的间接访问和增强。

代理模式的核心角色

  1. 目标对象(Subject):定义核心业务逻辑的接口或类,是代理对象最终要访问的对象。
  2. 代理对象(Proxy):实现与目标对象相同的接口,内部持有目标对象的引用,负责在调用目标对象前后添加额外操作,并最终转发请求给目标对象。

JavaScript 实现示例:图片懒加载代理

以下以 “图片懒加载” 为例实现代理模式。核心需求是:当图片进入可视区域时才加载真实图片,未进入时显示占位图,代理对象负责监听滚动事件、判断可视区域,并在合适时机触发真实图片的加载。

代码实现 - javascript
// -------------- 目标对象(真实图片加载器)--------------
class RealImageLoader {
  constructor(imgSrc) {
    this.imgSrc = imgSrc; // 真实图片地址
    this.imgElement = null; // 图片DOM元素
  }

  // 核心功能:加载真实图片
  load() {
    if (!this.imgElement) {
      this.imgElement = document.createElement('img');
      document.body.appendChild(this.imgElement);
    }
    // 加载真实图片
    this.imgElement.src = this.imgSrc;
    console.log(`加载真实图片:${this.imgSrc}`);
  }
}

// -------------- 代理对象(图片懒加载代理)--------------
class LazyLoadProxy {
  constructor(imgSrc, placeholder = 'placeholder.png') {
    // 持有目标对象的引用
    this.realLoader = new RealImageLoader(imgSrc);
    this.placeholder = placeholder; // 占位图地址
    this.imgElement = null;
    this.isLoaded = false; // 是否已加载真实图片

    // 初始化占位图
    this.initPlaceholder();
    // 绑定滚动监听(代理新增的逻辑)
    this.bindScrollEvent();
  }

  // 初始化占位图(代理的额外操作)
  initPlaceholder() {
    this.imgElement = document.createElement('img');
    this.imgElement.src = this.placeholder;
    document.body.appendChild(this.imgElement);
    console.log(`显示占位图:${this.placeholder}`);
  }

  // 监听滚动事件,判断是否进入可视区域(代理的额外操作)
  bindScrollEvent() {
    const checkVisibility = () => {
      if (this.isLoaded) return; // 已加载则停止检查

      const rect = this.imgElement.getBoundingClientRect();
      // 判断图片是否进入可视区域(上下左右均在窗口内)
      const isVisible = (
        rect.top < window.innerHeight &&
        rect.bottom > 0 &&
        rect.left < window.innerWidth &&
        rect.right > 0
      );

      if (isVisible) {
        // 进入可视区域,调用目标对象的加载方法
        this.realLoader.load();
        // 替换占位图为真实图片DOM(保持页面元素一致性)
        this.imgElement.parentNode.replaceChild(this.realLoader.imgElement, this.imgElement);
        this.isLoaded = true;
        // 卸载滚动监听(优化性能)
        window.removeEventListener('scroll', checkVisibility);
      }
    };

    // 初始检查一次,后续监听滚动
    checkVisibility();
    window.addEventListener('scroll', checkVisibility);
  }

  // 实现与目标对象相同的接口(保证调用一致性)
  load() {
    // 代理可直接触发加载(如手动提前加载)
    this.realLoader.load();
    this.isLoaded = true;
  }
}

// -------------- 使用示例 --------------
// 创建代理对象(而非直接使用RealImageLoader)
const img1 = new LazyLoadProxy('https://picsum.photos/800/600?random=1');
const img2 = new LazyLoadProxy('https://picsum.photos/800/600?random=2');
const img3 = new LazyLoadProxy('https://picsum.photos/800/600?random=3');

// 手动触发加载(可选)
// img3.load();

代码说明

  1. 目标对象(RealImageLoader):负责核心功能 —— 加载真实图片,对外提供 load 方法。
  2. 代理对象(LazyLoadProxy)
    • 持有 RealImageLoader 的实例,实现与目标对象相同的 load 方法(保证调用方无需区分代理和目标)。
    • 新增额外逻辑:初始化占位图、监听滚动事件、判断图片是否进入可视区域,仅在合适时机调用目标对象的 load 方法。
  3. 优势
    • 目标对象专注于核心功能(加载图片),代理对象承担非核心逻辑(懒加载),符合 “单一职责原则”。
    • 若未来需要修改懒加载逻辑(如调整可视区域判断规则),只需修改代理对象,无需改动目标对象,符合 “开闭原则”。

代理模式的常见应用场景

  • 权限控制:代理判断用户权限,无权限则拒绝访问目标对象。
  • 缓存代理:代理缓存目标对象的计算结果,重复请求时直接返回缓存,减少计算开销。
  • 日志代理:代理记录目标对象的调用参数、返回值等日志信息。
  • 延迟加载:如示例中的图片懒加载,或大型对象的按需初始化。

代理模式的核心是 “控制访问” 和 “增强功能”,通过分离核心逻辑与附加逻辑,提高代码的可维护性和扩展性。

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐