一、Proxy 详解
Proxy 用于创建一个对象的代理,从而实现对对象操作(如读取、赋值、删除等)的拦截和自定义处理。
核心语法
javascript
const proxy = new Proxy(target, handler);
target:被代理的目标对象(可以是任意类型对象,包括数组、函数、甚至另一个 Proxy)。handler:拦截配置对象,包含多个拦截方法(如get、set、deleteProperty等)。
常用拦截方法 & 示例
1. 拦截属性读取(get)
场景:给不存在的属性返回默认值,或禁止访问私有属性(以 _ 开头)。
javascript
const user = { name: "张三", age: 20, _id: "123456" };
const userProxy = new Proxy(user, {
get(target, prop) {
// 禁止访问私有属性
if (prop.startsWith("_")) {
throw new Error(`禁止访问私有属性 ${prop}`);
}
// 不存在的属性返回默认值
return target[prop] ?? "默认值";
},
});
console.log(userProxy.name); // 张三
console.log(userProxy.gender); // 默认值
console.log(userProxy._id); // 抛出错误:禁止访问私有属性 _id
2. 拦截属性赋值(set)
场景:校验属性赋值的合法性(如年龄必须是数字且范围合理)。
javascript
const user = { name: "张三", age: 20 };
const userProxy = new Proxy(user, {
set(target, prop, value) {
if (prop === "age") {
if (typeof value !== "number") {
throw new Error("年龄必须是数字");
}
if (value < 0 || value > 150) {
throw new Error("年龄范围必须在 0-150 之间");
}
}
// 合法则执行赋值
target[prop] = value;
// 必须返回 true 表示赋值成功(严格模式下不返回会报错)
return true;
},
});
userProxy.age = 25;
console.log(userProxy.age); // 25
userProxy.age = "三十"; // 抛出错误:年龄必须是数字
userProxy.age = 200; // 抛出错误:年龄范围必须在 0-150 之间
3. 拦截属性删除(deleteProperty)
场景:禁止删除核心属性。
javascript
const user = { name: "张三", age: 20 };
const userProxy = new Proxy(user, {
deleteProperty(target, prop) {
if (prop === "name") {
throw new Error("核心属性 name 禁止删除");
}
delete target[prop];
return true;
},
});
delete userProxy.age;
console.log(userProxy.age); // undefined
delete userProxy.name; // 抛出错误:核心属性 name 禁止删除
4. 拦截函数调用(apply)
场景:增强函数功能(如统计调用次数)。
javascript
function add(a, b) {
return a + b;
}
const addProxy = new Proxy(add, {
apply(target, thisArg, args) {
console.log(`函数被调用,参数:${args}`);
// 增强功能:参数转数字后计算
const newArgs = args.map((item) => Number(item));
return target(...newArgs);
},
});
console.log(addProxy("10", 20)); // 输出:函数被调用,参数:10,20 → 30
二、Reflect 详解
Reflect 是 ES6 新增的内置对象,提供了一组统一的方法来操作对象(对应 Object 的操作方法,如 Reflect.get 对应 obj[prop]),且所有方法都是静态的。
核心特点
- 方法与
Proxy的拦截方法一一对应(如Reflect.get对应Proxy的get拦截); - 操作对象的方法返回布尔值表示操作是否成功(如
Reflect.set返回true/false); - 避免
Object方法的怪异行为(如Object.defineProperty失败抛错,Reflect.defineProperty返回布尔值)。
常用方法 & 示例
1. 读取/赋值属性(Reflect.get/Reflect.set)
javascript
const user = { name: "张三", age: 20 };
// 读取属性
console.log(Reflect.get(user, "name")); // 张三
console.log(Reflect.get(user, "gender", "未知")); // 未知(第三个参数为默认值)
// 赋值属性
const setSuccess = Reflect.set(user, "age", 25);
console.log(setSuccess); // true
console.log(user.age); // 25
// 赋值失败(不可写属性)
const readonlyUser = Object.freeze({ name: "李四" });
console.log(Reflect.set(readonlyUser, "name", "王五")); // false
2. 删除属性(Reflect.deleteProperty)
javascript
const user = { name: "张三", age: 20 };
// 删除成功
console.log(Reflect.deleteProperty(user, "age")); // true
console.log(user.age); // undefined
// 删除失败(不可配置属性)
const nonConfigUser = {};
Object.defineProperty(nonConfigUser, "id", {
value: 123,
configurable: false,
});
console.log(Reflect.deleteProperty(nonConfigUser, "id")); // false
3. 定义属性(Reflect.defineProperty)
javascript
const user = {};
// 定义属性成功
const defineSuccess = Reflect.defineProperty(user, "name", {
value: "张三",
writable: true,
});
console.log(defineSuccess); // true
console.log(user.name); // 张三
// 定义属性失败(重复定义不可配置属性)
Reflect.defineProperty(user, "name", {
value: "李四",
configurable: false,
});
console.log(Reflect.defineProperty(user, "name", { value: "王五" })); // false
三、Proxy + Reflect 结合使用
Reflect 常配合 Proxy 使用,让拦截逻辑更规范(替代直接操作 target),且能自动处理 this 绑定问题。
示例:通用的对象校验代理
javascript
const validator = {
set(target, prop, value) {
// 用 Reflect.set 替代 target[prop] = value,返回布尔值更规范
const success = Reflect.set(target, prop, value);
// 自定义校验逻辑
if (prop === "age" && (value < 0 || value > 150)) {
console.warn("年龄范围不合法");
// 校验失败则回滚
Reflect.set(target, prop, target[prop]);
return false;
}
return success;
},
get(target, prop) {
// 用 Reflect.get 读取,支持默认值
return Reflect.get(target, prop, "默认值");
},
};
const user = new Proxy({ name: "张三", age: 20 }, validator);
user.age = 200; // 输出:年龄范围不合法
console.log(user.age); // 20(回滚为原值)
console.log(user.gender); // 默认值(Reflect.get 的默认值)
四、核心总结
| 特性 | Proxy | Reflect |
|---|---|---|
| 作用 | 拦截对象操作,自定义行为 | 提供标准化的对象操作方法 |
| 调用方式 | 实例化后通过代理对象操作 | 静态方法直接调用 |
| 返回值 | 代理对象 | 操作结果(布尔值/属性值等) |
| 典型场景 | 数据校验、权限控制、函数增强 | 配合 Proxy 简化拦截逻辑、安全操作对象 |
Proxy 是"拦截层",Reflect 是"标准化操作层",两者结合能优雅实现对象的自定义行为,是 ES6 中处理对象操作的最佳实践。