什么是proxy

在前端开发中,Proxy 是 ES6 引入的一个高级特性,用于拦截和自定义对象的基本操作(如属性访问、赋值、枚举、函数调用等)。它为开发者提供了元编程能力,是实现响应式系统、数据校验、访问控制等功能的核心技术。

一、基本概念

定义
Proxy 是一个构造函数,用于创建一个对象的代理,从而可以对该对象的基本操作进行拦截和自定义处理。

语法

const proxy = new Proxy(target, handler);

  • target:需要被代理的对象。
  • handler:包含拦截方法的对象,用于定义如何处理目标对象的操作。

二、核心拦截方法(常用)

1. get(target, property, receiver)

作用:拦截对象属性的读取操作。
示例

const person = { name: 'Doubao', age: 25 };

const proxy = new Proxy(person, {
  get(target, prop) {
    // 拦截属性读取,添加默认值
    return prop in target ? target[prop] : '未知属性';
  }
});

console.log(proxy.name);  // 输出: Doubao
console.log(proxy.gender); // 输出: 未知属性
2. set(target, property, value, receiver)

作用:拦截对象属性的赋值操作。
示例

const validator = {
  set(target, prop, value) {
    // 拦截属性赋值,添加校验逻辑
    if (prop === 'age') {
      if (typeof value !== 'number' || value < 0) {
        throw new Error('年龄必须为正整数');
      }
    }
    target[prop] = value;
    return true; // 必须返回 true 表示赋值成功
  }
};

const person = new Proxy({}, validator);
person.age = 25;     // 正常赋值
person.age = -5;     // 抛出错误
3. has(target, property)

作用:拦截 in 操作符。
示例

const handler = {
  has(target, prop) {
    // 隐藏以 _ 开头的私有属性
    return prop[0] !== '_' && prop in target;
  }
};

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

console.log('name' in proxy);  // 输出: true
console.log('_secret' in proxy); // 输出: false
4. deleteProperty(target, property)

作用:拦截 delete 操作符。
示例

const handler = {
  deleteProperty(target, prop) {
    // 禁止删除以 _ 开头的私有属性
    if (prop[0] === '_') {
      throw new Error('不能删除私有属性');
    }
    delete target[prop];
    return true;
  }
};

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

delete proxy.name;     // 正常删除
delete proxy._secret;  // 抛出错误

三、应用场景

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

Proxy 是 Vue 3 实现响应式数据的核心技术,相比 Vue 2 的 Object.defineProperty,它能拦截更多操作,包括新增属性、删除属性等。
示例

function reactive(target) {
  return new Proxy(target, {
    get(target, prop) {
      // 收集依赖
      track(target, prop);
      return target[prop];
    },
    set(target, prop, value) {
      target[prop] = value;
      // 触发更新
      trigger(target, prop);
      return true;
    }
  });
}

const state = reactive({ count: 0 });
// 当 state.count 变化时,自动触发更新
2. 数据校验与格式化

在赋值时自动校验数据类型或格式。
示例

const user = new Proxy({}, {
  set(target, prop, value) {
    if (prop === 'email') {
      const isValid = /^[\w-.]+@([\w-]+\.)+[\w-]{2,4}$/.test(value);
      if (!isValid) {
        throw new Error('邮箱格式不正确');
      }
    }
    target[prop] = value;
    return true;
  }
});

user.email = 'invalid'; // 抛出错误
user.email = 'valid@example.com'; // 正常赋值
3. 访问控制与私有属性

通过拦截 get 和 has 隐藏内部属性。
示例

const privateData = new WeakMap();

class User {
  constructor(name, age) {
    privateData.set(this, { name, age });
  }
  
  get publicInfo() {
    return new Proxy(privateData.get(this), {
      get(target, prop) {
        if (prop === 'age') {
          return '保密'; // 隐藏真实年龄
        }
        return target[prop];
      }
    });
  }
}

const user = new User('Doubao', 25);
console.log(user.publicInfo.name); // 输出: Doubao
console.log(user.publicInfo.age);  // 输出: 保密
4. 函数参数增强

拦截函数调用,添加参数校验或日志记录。
示例

function logFunction(fn) {
  return new Proxy(fn, {
    apply(target, thisArg, args) {
      console.log(`调用函数 ${target.name},参数:`, args);
      const result = target.apply(thisArg, args);
      console.log(`函数返回:`, result);
      return result;
    }
  });
}

const add = (a, b) => a + b;
const loggedAdd = logFunction(add);

loggedAdd(3, 5);
// 输出:
// 调用函数 add,参数: [3, 5]
// 函数返回: 8

四、Proxy 与 Object.defineProperty 的对比

特性ProxyObject.defineProperty
拦截范围所有基本操作(13 种拦截方法)仅能拦截属性的读取和赋值
新增 / 删除属性可以拦截无法拦截
数组操作可以拦截(如 push、pop)部分操作需特殊处理
深层响应式默认只处理一层,需递归创建代理默认只处理一层,需递归处理
性能现代浏览器优化较好,适用于复杂场景简单场景性能略优,但功能受限

五、注意事项

  1. 兼容性
    Proxy 是 ES6 特性,不支持 IE 浏览器,需通过 Babel 等工具编译或提供降级方案。

  2. 内存管理
    Proxy 对象会保持对目标对象的引用,可能导致内存泄漏,需注意适时释放。

  3. 递归代理
    对于嵌套对象,需递归创建 Proxy 才能实现深层拦截。

  4. Reflect 对象
    推荐结合 Reflect 对象使用,以保持操作的默认行为:

    const proxy = new Proxy(target, {
      get(target, prop, receiver) {
        return Reflect.get(target, prop, receiver);
      }
    });
    

通过合理使用 Proxy,开发者可以实现更灵活、高效的元编程,提升代码的可维护性和健壮性。

JDK Proxy是JDK提供的基于接口的代理功能,通过`java.reflect.Proxy`代理类实现。由`Proxy.newProxyInstance(ClassLoader interfaceClassLoader, Class<?>[] interfaces, InvocationHandler handler)`创建的对象属于`java.reflect.Proxy`实例。由于Java单继承的特性,JDK Proxy无法对类进行代理。生成的类默认重载了`java.lang.Object`的`hashCode`、`toString`、`equals`方法,调用`proxy.hashCode`会间接调用`Proxy`的`InvocationHandler`的实现。同时,实现了`InvocationHandler`接口的代理类,会继承一个`invoke`方法,可在该方法中添加代码,在方法前后添加动态内容。不过,JDK Proxy依赖于接口,若类没有接口则不能实现动态代理[^1][^2]。 以下是一个简单的JDK Proxy示例: ```java package com.test.reflect.jdk; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; // 定义接口 interface MyInterface { void doSomething(); } // 实现接口的类 class MyClass implements MyInterface { @Override public void doSomething() { System.out.println("Doing something..."); } } // 实现InvocationHandler接口 class MyHandler implements InvocationHandler { static final private Logger logger = LoggerFactory.getLogger(MyHandler.class); private Object object = null; public MyHandler(Object object) { this.object = object; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { logger.info("do something before method {}", method.getName()); Object ret = method.invoke(this.object, args); logger.info("do something after method {}", method.getName()); return ret; } } // 测试类 public class JDKProxyTest { public static void main(String[] args) { MyClass myClass = new MyClass(); MyHandler myHandler = new MyHandler(myClass); MyInterface proxyInstance = (MyInterface) Proxy.newProxyInstance( MyInterface.class.getClassLoader(), new Class<?>[]{MyInterface.class}, myHandler ); proxyInstance.doSomething(); } } ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值