前言
在前端开发中,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 |
注意事项:
- 性能:Proxy 的拦截操作比直接访问属性稍慢,避免在性能敏感场景滥用。
- 兼容性:Proxy 不支持 IE 浏览器(可通过
Proxypolyfill 部分实现,但有局限性)。 - 调试:Proxy 包装的对象在控制台显示为
Proxy,可能增加调试复杂度。
合理使用 Proxy 可以大幅提升代码的灵活性和可维护性,但需权衡其带来的开销。