ES6 中的 ​​Proxy​​​详细使用方法

14 阅读2分钟

ES6 的 Proxy 对象用于创建一个对象的代理,从而拦截并自定义对象的基本操作(如属性查找、赋值、枚举等)。它是 JavaScript 元编程(meta-programming)的核心工具之一。以下是详细使用方法:


1. 基本语法

const proxy = new Proxy(target, handler);
  • target: 被代理的目标对象(可以是任何类型)
  • handler: 包含拦截操作(traps)的对象,定义代理行为

2. 常用拦截器(Traps)

(1) get:拦截属性读取

const handler = {
  get(target, prop) {
    return prop in target ? target[prop] : '默认值';
  }
};

const obj = { name: 'John' };
const proxy = new Proxy(obj, handler);

console.log(proxy.name); // 'John'
console.log(proxy.age);  // '默认值'

(2) set:拦截属性赋值

const handler = {
  set(target, prop, value) {
    if (prop === 'age' && typeof value !== 'number') {
      throw new TypeError('Age must be a number!');
    }
    target[prop] = value;
    return true; // 表示成功
  }
};

const user = {};
const proxy = new Proxy(user, handler);

proxy.age = 30; // 成功
proxy.age = 'old'; // 抛出 TypeError

(3) has:拦截 in 操作符

const handler = {
  has(target, prop) {
    if (prop.startsWith('_')) {
      return false; // 隐藏私有属性
    }
    return prop in target;
  }
};

const obj = { _secret: '123', public: 'data' };
const proxy = new Proxy(obj, handler);

console.log('_secret' in proxy); // false
console.log('public' in proxy);  // true

(4) deleteProperty:拦截 delete 操作

const handler = {
  deleteProperty(target, prop) {
    if (prop.startsWith('_')) {
      throw new Error('Cannot delete private properties!');
    }
    delete target[prop];
    return true;
  }
};

const obj = { _id: 1, name: 'Alice' };
const proxy = new Proxy(obj, handler);

delete proxy.name; // 成功
delete proxy._id;  // 抛出错误

(5) apply:拦截函数调用

const handler = {
  apply(target, thisArg, args) {
    console.log(`Called with args: ${args}`);
    return target(...args) * 2; // 修改返回值
  }
};

function sum(a, b) { return a + b; }

const proxy = new Proxy(sum, handler);
console.log(proxy(2, 3)); // 输出日志,返回 10

(6) construct:拦截 new 操作

const handler = {
  construct(target, args) {
    console.log(`Creating instance with args: ${args}`);
    return new target(...args); // 必须返回对象
  }
};

class Person {
  constructor(name) { this.name = name; }
}

const ProxyPerson = new Proxy(Person, handler);
const p = new ProxyPerson('Bob'); // 输出日志

3. 其他拦截器

拦截器触发操作
getPrototypeOfObject.getPrototypeOf(proxy)
setPrototypeOfObject.setPrototypeOf(proxy, proto)
isExtensibleObject.isExtensible(proxy)
preventExtensionsObject.preventExtensions(proxy)
getOwnPropertyDescriptorObject.getOwnPropertyDescriptor(proxy, prop)
definePropertyObject.defineProperty(proxy, prop, desc)
ownKeysObject.keys(proxy)Object.getOwnPropertyNames(proxy)

4. 使用场景

  • 数据验证:拦截属性赋值并验证
  • 格式化/转换:自动转换输入值(如字符串转数字)
  • 观察者模式:监听对象变化
  • API 封装:保护敏感操作或属性
  • 性能优化:延迟加载对象属性

5. 可撤销代理

使用 Proxy.revocable() 创建可取消的代理:

const { proxy, revoke } = Proxy.revocable({}, handler);

proxy.name = 'Test'; // 正常操作
revoke(); // 撤销代理

proxy.name; // TypeError: Cannot perform 'get' on a revoked proxy

6. 注意事项

  1. 代理不是深拷贝:修改代理会影响原始对象
  2. 目标对象不可变handler 无法修改 target 的引用
  3. 性能开销:代理操作比直接访问略慢(在性能关键代码中慎用)
  4. 透明性proxy instanceof TargetClass 仍返回 true

完整示例:数据验证代理

const validator = {
  set(target, prop, value) {
    if (prop === 'age') {
      if (typeof value !== 'number' || value < 0) {
        throw new TypeError('Invalid age value');
      }
    }
    target[prop] = value;
    return true;
  }
};

const person = {};
const proxy = new Proxy(person, validator);

proxy.age = 25; // 成功
proxy.age = -5; // 抛出错误

通过灵活使用 Proxy,可以实现高度自定义的对象行为控制,为复杂逻辑提供优雅解决方案。