🤔

代理模式(Proxy Pattern)

1)从一个“需求/问题”引入:为什么我需要代理?

你可能遇到过这些前端场景:

  • 图片很多的列表页,首屏加载慢:能不能“先占位,滚动到可视区再加载真实图片”?
  • 点击按钮会触发昂贵请求:能不能“相同参数的请求别重复打,直接复用结果”?
  • 有些操作需要权限校验/登录态:能不能“先拦一下,不符合条件就不让继续执行”?
  • 表单输入频繁触发查询:能不能“别每个字符都请求,做防抖/节流”?

这些诉求背后都有共同点:我想在真正执行某个对象/函数的核心逻辑之前或之后,加一层控制
这就是代理模式非常擅长解决的问题。


2)代理模式的关键:它到底是什么?

代理模式(Proxy Pattern):为目标对象/函数提供一个“代理”,由代理来控制对目标的访问。

你可以把它理解成:

  • 目标(Real Subject):真正干活的人/对象
  • 代理(Proxy):中间人/门卫/缓存层,负责“拦截、增强、限制、转发”
  • 调用方(Client):只跟代理打交道(也可以无感)

代理模式的核心价值(前端视角)

  • 访问控制:权限、登录态、黑白名单、环境判断
  • 性能优化:缓存、懒加载、合并请求、节流/防抖
  • 解耦增强:不改目标代码,在外面加日志、埋点、错误处理

一句话抓重点:

不改变原对象核心逻辑,通过代理添加“控制与增强”。


3)简单代码例子:用“缓存代理”避免重复请求

下面用一个非常常见的场景:同一个请求参数在短时间内重复触发(比如重复点击、多个组件请求同一资源)。我们用代理做缓存。

// 目标函数:真正的请求(这里用 setTimeout 模拟网络请求)
function fetchUserFromServer(userId) {
  console.log("[server] fetching user:", userId);
  return new Promise((resolve) => {
    setTimeout(() => resolve({ id: userId, name: "User_" + userId }), 300);
  });
}

// 代理:增加缓存能力(memoization)
function createCachedFetch(fetchFn) {
  const cache = new Map(); // key -> Promise

  return function cachedFetch(key) {
    if (cache.has(key)) {
      console.log("[proxy] hit cache:", key);
      return cache.get(key);
    }

    console.log("[proxy] miss cache, request:", key);
    const promise = fetchFn(key).catch((err) => {
      // 注意:失败的请求一般不应缓存,避免“永久失败”
      cache.delete(key);
      throw err;
    });

    cache.set(key, promise);
    return promise;
  };
}

const fetchUser = createCachedFetch(fetchUserFromServer);

// demo:连续调用两次同一个 userId,只会发一次“真实请求”
fetchUser(1).then(console.log);
fetchUser(1).then(console.log);
fetchUser(2).then(console.log);

你会看到输出大概是:

  • 第一次 fetchUser(1):代理 miss,走真实请求
  • 第二次 fetchUser(1):代理 hit,直接复用第一次的 Promise
  • fetchUser(2):新参数,新请求

这就是典型的代理增强:调用方无需知道缓存细节。


4)前端常见应用场景(很实用)

4.1 虚拟代理:图片懒加载 / 占位符

  • 先展示占位图,进入可视区再加载真实资源
  • 常见于瀑布流、长列表、详情页多图

4.2 缓存代理:请求结果缓存 / 计算结果缓存

  • 相同参数直接返回缓存
  • 适合:字典数据、配置、幂等查询、昂贵计算(如大 JSON 解析)

4.3 保护代理:权限控制 / 登录校验

  • 调用前先判断是否登录/是否有权限
  • 适合:支付、下单、查看敏感信息、管理后台按钮

4.4 代理作为“统一切面”:日志、埋点、错误处理

  • 在函数调用前后统一加埋点和耗时统计
  • 在请求层统一做错误兜底、重试、熔断

4.5 函数代理:防抖/节流(本质上也是“控制访问频率”)

  • 输入框联想:防抖
  • 滚动监听:节流

5)注意事项:代理模式不是“万能中间层”

  1. 别滥用导致调用链过深
    代理层太多会让调试困难:到底是哪里改了参数、哪里吞了错误?

  2. 缓存要考虑失效策略

    • 什么时候过期(TTL)?
    • 是否需要手动清理?
    • 是否区分用户、环境、权限(避免串数据)?
  3. Promise 缓存要特别注意失败缓存
    通常失败不缓存,否则一次失败可能导致后续一直失败(上面的示例用 catch 删除缓存就是为此)。

  4. 代理里的副作用要可控
    例如埋点、日志、重试等,不要影响原本的业务语义(比如改变返回值类型、吞异常)。

  5. 代理不等于 ES6 的 Proxy 语法糖
    设计模式讲的是思想:你可以用闭包/高阶函数/类来实现。ES6 Proxy 只是其中一种实现手段。


小结

  • 代理模式解决的是:在不改目标逻辑的前提下,控制访问并进行增强
  • 前端最常用:缓存、懒加载、权限控制、埋点、错误处理、防抖节流
  • 用得好能显著提升性能与可维护性;用得过度会让系统“黑盒化”。