ES6——Proxy实际应用

505 阅读2分钟

前言

在前端开发中,Proxy(代理)是 ES6 引入的元编程特性,用于拦截和自定义对象的基本操作。以下是 Proxy 的经典应用场景及代码示例:

1. 数据响应式(如 Vue 3 的响应式系统)

通过 Proxy 拦截对象属性的 读取(get)  和 修改(set) ,实现数据变化时的自动更新。

function reactive(obj) {
  return new Proxy(obj, {
    get(target, key) {
      console.log(`读取属性 ${key}`);
      return Reflect.get(target, key);
    },
    set(target, key, value) {
      console.log(`更新属性 ${key}${value}`);
      return Reflect.set(target, key, value);
    },
  });
}

const data = reactive({ count: 0 });
data.count = 1; // 输出: "更新属性 count 为 1"
console.log(data.count); // 输出: "读取属性 count" → 1

2. 表单验证

拦截表单对象的赋值操作,实时验证输入合法性。

const formValidator = {
  set(target, key, value) {
    if (key === 'age' && (typeof value !== 'number' || value < 0)) {
      throw new Error('年龄必须是非负数字');
    }
    return Reflect.set(target, key, value);
  },
};

const form = new Proxy({}, formValidator);
form.age = 25; // 正常
form.age = -5; // 抛出错误: "年龄必须是非负数字"

3. API 请求拦截与封装

统一处理请求参数和响应,例如自动添加 Token、错误处理。

const apiHandler = {
  get(target, method) {
    return (url, data) => {
      console.log(`请求 ${method.toUpperCase()} ${url}`, data);
      // 实际发送请求(如 fetch 或 axios)
      return fetch(url, { method, body: JSON.stringify(data) })
        .then(res => res.json())
        .catch(error => ({ error: true, message: error.message }));
    };
  },
};

const api = new Proxy({}, apiHandler);
api.get('/user', { id: 1 }); // 输出: "请求 GET /user" → 发送请求
api.post('/submit', { name: 'Alice' }); // 输出: "请求 POST /submit"

4. 权限控制

拦截对象属性的访问或修改,根据权限动态控制行为。

const user = { name: 'Alice', role: 'admin' };

const secureProxy = new Proxy(user, {
  get(target, key) {
    if (key === 'password' && user.role !== 'admin') {
      throw new Error('无权访问密码');
    }
    return Reflect.get(target, key);
  },
});

console.log(secureProxy.name); // "Alice"
console.log(secureProxy.password); // 若 role 不是 admin,抛出错误

5. 日志记录与调试

自动记录对象操作,方便调试和审计。

const withLogging = (obj) => {
  return new Proxy(obj, {
    get(target, key) {
      console.log(`读取属性: ${key}`);
      return Reflect.get(target, key);
    },
    set(target, key, value) {
      console.log(`设置属性 ${key}${value}`);
      return Reflect.set(target, key, value);
    },
  });
};

const obj = withLogging({ a: 1 });
obj.a = 2; // 输出: "设置属性 a 为 2"
console.log(obj.a); // 输出: "读取属性: a" → 2

6. 缓存优化(Memoization)

拦截方法调用,缓存结果避免重复计算。

function memoize(fn) {
  const cache = new Map();
  return new Proxy(fn, {
    apply(target, thisArg, args) {
      const key = JSON.stringify(args);
      if (cache.has(key)) {
        console.log('返回缓存结果');
        return cache.get(key);
      }
      const result = Reflect.apply(target, thisArg, args);
      cache.set(key, result);
      return result;
    },
  });
}

const heavyCompute = memoize((a, b) => {
  console.log('执行计算...');
  return a + b;
});

heavyCompute(2, 3); // 输出: "执行计算..." → 5
heavyCompute(2, 3); // 输出: "返回缓存结果" → 5

7. 动态属性生成

拦截不存在的属性访问,按需生成或返回默认值。

const dynamicProps = new Proxy({}, {
  get(target, key) {
    if (!(key in target)) {
      console.log(`动态创建属性 ${key}`);
      target[key] = `默认值-${key}`;
    }
    return Reflect.get(target, key);
  },
});

console.log(dynamicProps.foo); // 输出: "动态创建属性 foo" → "默认值-foo"
console.log(dynamicProps.foo); // 直接返回缓存值 → "默认值-foo"

总结

场景Proxy 的作用关键拦截操作
数据响应式自动追踪依赖、触发更新get/set
表单验证实时校验输入合法性set
API 拦截统一处理请求/响应逻辑get(方法调用)
权限控制动态限制属性访问get/set
日志记录自动记录对象操作get/set/apply
缓存优化避免重复计算或请求apply
动态属性按需生成属性或默认值get

注意事项

  1. 性能:Proxy 的拦截操作比直接访问属性稍慢,避免在性能敏感场景滥用。
  2. 兼容性:Proxy 不支持 IE 浏览器(可通过 Proxy polyfill 部分实现,但有局限性)。
  3. 调试:Proxy 包装的对象在控制台显示为 Proxy,可能增加调试复杂度。

合理使用 Proxy 可以大幅提升代码的灵活性和可维护性,但需权衡其带来的开销。