在前端开发中,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); // 输出: false4.
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 的对比
五、注意事项兼容性:
Proxy 是 ES6 特性,不支持 IE 浏览器,需通过 Babel 等工具编译或提供降级方案。内存管理:
Proxy 对象会保持对目标对象的引用,可能导致内存泄漏,需注意适时释放。递归代理:
对于嵌套对象,需递归创建 Proxy 才能实现深层拦截。Reflect 对象:
推荐结合 Reflect 对象使用,以保持操作的默认行为:const proxy = new Proxy(target, { get(target, prop, receiver) { return Reflect.get(target, prop, receiver); } });通过合理使用 Proxy,开发者可以实现更灵活、高效的元编程,提升代码的可维护性和健壮性。