📌 前言
在现代 JavaScript 开发中,我们常常希望"在不入侵原始逻辑的前提下",给某些操作加上一些附加功能,比如:
- 给方法调用打日志、做埋点
- 拦截和校验属性的读取与修改
- 在函数执行前后做增强(AOP)
这正是 Proxy
与 Reflect
的组合大展拳脚的舞台。
🧠 一、什么是 Proxy?
Proxy
是 ES6 引入的元编程工具,允许你"代理"一个对象,对其属性访问、设置、函数调用、构造行为等进行全面拦截。
✨ 基本使用
1️⃣ Proxy 代理普通对象
javascript
const user = { name: "Alice", age: 25 };
const proxyUser = new Proxy(user, {
get(target, prop) {
console.log(`读取了属性 ${prop}`);
return Reflect.get(target, prop);
},
set(target, prop, value) {
console.log(`设置了属性 ${prop} = ${value}`);
return Reflect.set(target, prop, value);
}
});
console.log(proxyUser.name); // 读取了属性 name
proxyUser.age = 26; // 设置了属性 age = 26
📌 场景:数据校验、数据绑定、调试埋点等。
2️⃣ Proxy 代理函数
函数其实也是对象,我们可以通过 Proxy 拦截函数调用、构造行为(构造函数才可以拦截构造行为)。
javascript
function greet(name) {
return `Hello, ${name}!`;
}
const proxyGreet = new Proxy(greet, {
apply(target, thisArg, args) {
console.log(`调用函数 greet,参数:${args}`);
return Reflect.apply(target, thisArg, args);
},
construct(target, args) {
console.log(`通过 new 构造函数调用 greet,参数:${args}`);
return Reflect.construct(target, args);
}
});
console.log(proxyGreet("Bob")); // 调用函数 greet,参数:Bob -> Hello, Bob!
// new proxyGreet("Alice"); // 如果 greet 不是构造函数,这行会抛错
⚡ 应用:日志记录、参数校验、权限控制等。
3️⃣ Proxy 代理类
类本质上是构造函数,可以拦截实例化操作。
javascript
class Person {
constructor(name) {
this.name = name;
}
sayHi() {
return `Hi, I am ${this.name}`;
}
}
const ProxyPerson = new Proxy(Person, {
construct(target, args) {
console.log(`实例化 Person,参数:${args}`);
return Reflect.construct(target, args);
}
});
const p = new ProxyPerson("Charlie"); // 实例化 Person,参数:Charlie
console.log(p.sayHi()); // Hi, I am Charlie
🔑 适用:实例化控制、日志收集、权限管理等。
🧩 二、与 Reflect 的配合(保持语义)
Reflect
是 ES6 提供的工具对象,它将原本可能隐式完成的底层操作变成显式函数调用,用于安全、语义明确地代理目标对象。
Reflect 方法 | 用途 | 参数说明 | 返回值 |
---|---|---|---|
Reflect.get(target, prop, receiver) |
获取对象属性 | 目标对象,属性名,访问器上下文 | 属性值 |
Reflect.set(target, prop, value, receiver) |
设置对象属性 | 目标对象,属性名,属性值,赋值上下文 | 布尔值,是否设置成功 |
Reflect.apply(targetFn, thisArg, args) |
调用函数 | 目标函数,this绑定,参数列表 | 函数调用结果 |
Reflect.construct(targetConstructor, args, newTarget) |
构造函数调用 | 构造函数,参数列表,new.target(可选) | 新实例对象 |
示例:Reflect.apply 调用函数
javascript
function sum(a, b) {
return a + b;
}
console.log(Reflect.apply(sum, null, [3, 4])); // 7
示例:Reflect.construct 实例化对象
javascript
function Animal(type) {
this.type = type;
}
const dog = Reflect.construct(Animal, ['dog']);
console.log(dog.type); // dog
🎯 三、AOP(面向切面编程)简介
AOP 是一种编程范式,它将"横切关注点"从主逻辑中解耦,如:
- 日志记录
- 性能监控
- 权限校验
- 异常捕获
在 JavaScript 中,我们可通过 Proxy + Reflect
轻松实现 AOP 功能!
🔧 四、封装通用 AOP 工具
下面封装一个通用 AOP 工具,支持对象实例、函数和类的代理,自动在调用前后执行自定义逻辑:
javascript
function withAOP(target, { before, after, error }) {
if (typeof target === "function") {
// 代理函数或类
return new Proxy(target, {
apply(fn, thisArg, args) {
try {
before?.(fn.name, args);
const result = Reflect.apply(fn, thisArg, args);
after?.(fn.name, result, args);
return result;
} catch (err) {
error?.(fn.name, err, args);
throw err;
}
},
construct(fn, args) {
try {
before?.(fn.name, args);
const instance = Reflect.construct(fn, args);
after?.(fn.name, instance, args);
return instance;
} catch (err) {
error?.(fn.name, err, args);
throw err;
}
}
});
} else if (typeof target === "object") {
// 代理对象实例
return new Proxy(target, {
get(obj, prop, receiver) {
const value = Reflect.get(obj, prop, receiver);
if (typeof value === "function") {
return new Proxy(value, {
apply(fn, thisArg, args) {
try {
before?.(prop, args);
const result = Reflect.apply(fn, thisArg, args);
after?.(prop, result, args);
return result;
} catch (err) {
error?.(prop, err, args);
throw err;
}
}
});
}
return value;
}
});
}
}
✅ 五、实际应用场景
1️⃣ 日志埋点
javascript
const service = {
fetchData(id) {
return `data-${id}`;
}
};
const wrapped = withAOP(service, {
before: (method, args) => console.log(`[调用] ${method}(${args})`),
after: (method, result) => console.log(`[结果] ${method} => ${result}`)
});
wrapped.fetchData(123);
2️⃣ 表单数据拦截验证
javascript
const form = {};
const proxyForm = new Proxy(form, {
set(target, prop, value) {
if (prop === 'email' && !/^\S+@\S+.\S+$/.test(value)) {
throw new Error('邮箱格式不合法');
}
return Reflect.set(target, prop, value);
}
});
proxyForm.email = 'test@example.com'; // ✅
proxyForm.email = 'invalid'; // ❌ 抛出异常
3️⃣ 权限控制
javascript
function withAdminCheck(obj) {
return new Proxy(obj, {
get(target, prop) {
if (prop === 'deleteUser') {
throw new Error('你没有权限执行该操作');
}
return Reflect.get(target, prop);
}
});
}
🚫 六、Proxy 的性能与限制
- 会略微降低性能,尤其在高频调用、大量属性的对象上
- 无法代理
JSON.stringify
、Object.keys
的默认行为(需手动处理) - 不兼容 IE11,需 polyfill 或降级方案
最佳实践:
- ✔ 用于核心功能外的"横切逻辑"
- ✔ 用于开发阶段调试增强(埋点、日志)
- ✔ Reflect 保持操作语义,建议代理内部使用 Reflect 来确保行为一致性
- ❌ 避免在性能关键路径大量使用(如每个数据行都用 Proxy)
💡 七、延伸阅读
- MDN Proxy 文档
- Reflect 官方文档
-
《你不知道的JavaScript》:元编程章节\](广告位招租ʕ•́ᴥ•̀ʔ )
🧾 总结
技术 | 作用 |
---|---|
Proxy | 拦截对象所有行为(访问、赋值、调用等) |
Reflect | 标准、安全地执行默认行为 |
AOP | 在函数调用前后插入日志/校验/埋点等横切逻辑 |
📦 结语
随着前端应用逻辑日趋复杂,模块化、解耦、透明增强变得越来越重要。掌握 Proxy + Reflect + AOP
这套组合拳,将极大提升你的架构能力和代码可维护性。
喜欢这篇文章,记得点赞👍、收藏⭐,分享给更多朋友!
如果有疑问或者想深入交流,欢迎评论留言!🎈