前言
在现代 JavaScript 开发中,我们常常希望“在不入侵原始逻辑的前提下”,给某些操作加上一些附加功能,比如:
- 给方法调用打日志、做埋点
- 拦截和校验属性的读取与修改
- 在函数执行前后做增强(AOP)
这正是 Proxy
与 Reflect
的组合大展拳脚的舞台。
一、什么是 Proxy?
Proxy
是 ES6 引入的元编程工具,允许你“代理”一个对象,对其属性访问、设置、函数调用、构造行为等进行全面拦截。
基本使用
1. Proxy 代理普通对象
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 拦截函数调用、构造行为(构造函数才可以拦截构造行为)。
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 代理类
类本质上是构造函数,可以拦截实例化操作。
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 调用函数
function sum(a, b) {
return a + b;
}
console.log(Reflect.apply(sum, null, [3, 4])); // 7
示例:Reflect.construct 实例化对象
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 工具,支持对象实例、函数和类的代理,自动在调用前后执行自定义逻辑:
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. 日志埋点
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. 表单数据拦截验证
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. 权限控制
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)
七、延伸阅读
总结
技术 | 作用 |
---|---|
Proxy | 拦截对象所有行为(访问、赋值、调用等) |
Reflect | 标准、安全地执行默认行为 |
AOP | 在函数调用前后插入日志/校验/埋点等横切逻辑 |
结语
随着前端应用逻辑日趋复杂,模块化、解耦、透明增强变得越来越重要。掌握 Proxy + Reflect + AOP
这套组合拳,将极大提升你的架构能力和代码可维护性。