在现代 JavaScript 开发中,代理(Proxy) 与 反射(Reflect) 是两个非常强大的特性。
它们可以帮助我们 拦截对象操作、控制属性访问、实现验证逻辑,甚至实现响应式系统 。
下面我们按照知识点逐一展开。
1. 创建空代理
最基本的代理写法如下:
ini
const target = { name: "Alice" };
const proxy = new Proxy(target, {}); // 空代理,没有拦截行为
proxy.age = 20;
console.log(proxy.name); // "Alice"
console.log(target.age); // 20
👉 空代理只是 target
的一个"镜像",没有任何特殊功能。
2. 定义捕获器(Traps)
捕获器就是代理的"拦截器"。例如 get
捕获属性访问:
javascript
const target = { name: "Alice" };
const proxy = new Proxy(target, {
get(obj, prop) {
console.log(`访问属性: ${prop}`);
return obj[prop];
}
});
console.log(proxy.name); // 输出: 访问属性: name \n "Alice"
3. 捕获器与参数
大多数捕获器会接收 target、prop、receiver 参数。例如:
javascript
const target = { age: 25 };
const proxy = new Proxy(target, {
get(t, prop, receiver) {
console.log(`读取属性: ${prop}`);
return Reflect.get(t, prop, receiver); // 推荐用 Reflect 保持一致性
}
});
console.log(proxy.age); // 输出: 读取属性: age \n 25
4. 捕获器不变式
代理必须遵循 JavaScript 对象的不变式(invariants),否则会抛错。
javascript
// 示例一
const obj = Object.freeze({ x: 10 });
const proxy = new Proxy(obj, {
get() {
return 42; // ❌ 不能篡改冻结对象的不变式
}
});
console.log(proxy.x); // 10,而不是 42
// 示例二
const obj = {};
Object.defineProperty(obj, "y", {
value: 100,
writable: false, // ❌ 不可写
configurable: false // ❌ 不可重新定义
});
const proxy = new Proxy(obj, {
get() {
return 200; // ❌ 尝试违反不变式
}
});
console.log(proxy.y);
// 输出: 100
// 说明:代理必须保持与目标对象一致,
// 不允许修改不可写 + 不可配置属性的值
5. 可撤销代理
代理可以通过 Proxy.revocable
创建,并支持 撤销。
javascript
const { proxy, revoke } = Proxy.revocable({ msg: "Hello" }, {
get(t, prop) {
return t[prop];
}
});
console.log(proxy.msg); // "Hello"
revoke(); // 撤销代理
// console.log(proxy.msg); // ❌ 报错:代理已撤销
6. 实用反射 API
Reflect
提供了一套方法,和代理捕获器一一对应。
javascript
const obj = { x: 10 };
console.log(Reflect.get(obj, "x")); // 10
Reflect.set(obj, "y", 20);
console.log(obj.y); // 20
👉 使用 Reflect
操作对象,比直接操作更安全规范。
7. 代理另一个代理
代理本身也能被再次代理:
javascript
const target = { value: 1 };
const proxy1 = new Proxy(target, {
get(t, prop) {
console.log("proxy1 get");
return Reflect.get(t, prop);
}
});
const proxy2 = new Proxy(proxy1, {
get(t, prop) {
console.log("proxy2 get");
return Reflect.get(t, prop);
}
});
console.log(proxy2.value);
// 输出:proxy2 get \n proxy1 get \n 1
8. 代理的问题与不足
- 性能开销:拦截操作有额外消耗。
- 调试困难:过度使用会让逻辑难以理解。
- 部分内建对象(如 DOM 节点)可能无法完全代理。
9. 代理捕获器与反射方法
每个捕获器都有对应的 Reflect
方法,推荐配合使用。下面给出示例:
9.1 get ↔ Reflect.get
javascript
const obj = { name: "Alice" };
const proxy = new Proxy(obj, {
get(t, prop, receiver) {
console.log(`读取 ${prop}`);
return Reflect.get(t, prop, receiver);
}
});
console.log(proxy.name); // 输出: 读取 name \n Alice
9.2 set ↔ Reflect.set
javascript
const obj = {};
const proxy = new Proxy(obj, {
set(t, prop, value, receiver) {
console.log(`设置 ${prop} = ${value}`);
return Reflect.set(t, prop, value, receiver);
}
});
proxy.age = 30; // 输出: 设置 age = 30
9.3 has ↔ Reflect.has
javascript
const obj = { x: 10 };
const proxy = new Proxy(obj, {
has(t, prop) {
console.log(`检查是否有 ${prop}`);
return Reflect.has(t, prop);
}
});
console.log("x" in proxy); // 输出: 检查是否有 x \n true
9.4 defineProperty ↔ Reflect.defineProperty
javascript
const obj = {};
const proxy = new Proxy(obj, {
defineProperty(t, prop, desc) {
console.log(`定义属性 ${prop}`);
return Reflect.defineProperty(t, prop, desc);
}
});
Object.defineProperty(proxy, "name", { value: "Bob" });
console.log(obj.name); // Bob
9.5 getOwnPropertyDescriptor ↔ Reflect.getOwnPropertyDescriptor
javascript
const obj = { a: 1 };
const proxy = new Proxy(obj, {
getOwnPropertyDescriptor(t, prop) {
console.log(`获取属性描述符 ${prop}`);
return Reflect.getOwnPropertyDescriptor(t, prop);
}
});
console.log(Object.getOwnPropertyDescriptor(proxy, "a"));
9.6 deleteProperty ↔ Reflect.deleteProperty
javascript
const obj = { secret: "123" };
const proxy = new Proxy(obj, {
deleteProperty(t, prop) {
console.log(`删除属性 ${prop}`);
return Reflect.deleteProperty(t, prop);
}
});
delete proxy.secret; // 输出: 删除属性 secret
9.7 ownKeys ↔ Reflect.ownKeys
javascript
const obj = { x: 1, y: 2 };
const proxy = new Proxy(obj, {
ownKeys(t) {
console.log("获取所有键");
return Reflect.ownKeys(t);
}
});
console.log(Object.keys(proxy)); // 输出: 获取所有键 \n ["x","y"]
9.8 getPrototypeOf ↔ Reflect.getPrototypeOf
javascript
const obj = {};
const proxy = new Proxy(obj, {
getPrototypeOf(t) {
console.log("获取原型");
return Reflect.getPrototypeOf(t);
}
});
console.log(Object.getPrototypeOf(proxy));
9.9 setPrototypeOf ↔ Reflect.setPrototypeOf
javascript
const obj = {};
const proto = { greet: () => "hi" };
const proxy = new Proxy(obj, {
setPrototypeOf(t, proto) {
console.log("设置原型");
return Reflect.setPrototypeOf(t, proto);
}
});
Object.setPrototypeOf(proxy, proto); // 输出: 设置原型
console.log(obj.greet()); // hi
9.10 isExtensible ↔ Reflect.isExtensible
javascript
const obj = {};
const proxy = new Proxy(obj, {
isExtensible(t) {
console.log("检查是否可扩展");
return Reflect.isExtensible(t);
}
});
console.log(Object.isExtensible(proxy)); // true
9.11 preventExtensions ↔ Reflect.preventExtensions
javascript
const obj = {};
const proxy = new Proxy(obj, {
preventExtensions(t) {
console.log("禁止扩展");
return Reflect.preventExtensions(t);
}
});
Object.preventExtensions(proxy); // 输出: 禁止扩展
9.12 apply ↔ Reflect.apply
javascript
function sum(a, b) { return a + b; }
const proxy = new Proxy(sum, {
apply(fn, thisArg, args) {
console.log("调用函数:", args);
return Reflect.apply(fn, thisArg, args);
}
});
console.log(proxy(2, 3)); // 输出: 调用函数: [2,3] \n 5
9.13 construct ↔ Reflect.construct
javascript
function Person(name) { this.name = name; }
const proxy = new Proxy(Person, {
construct(t, args, newTarget) {
console.log("构造函数调用:", args);
return Reflect.construct(t, args, newTarget);
}
});
const p = new proxy("Alice");
// 输出: 构造函数调用: [ 'Alice' ]
console.log(p.name); // Alice
10. 跟踪属性访问
javascript
function track(obj) {
return new Proxy(obj, {
get(t, prop) {
console.log(`访问 ${prop}`);
return Reflect.get(t, prop);
}
});
}
const user = track({ name: "Tom", age: 20 });
console.log(user.name);
11. 隐藏属性
javascript
function hide(obj, keys) {
return new Proxy(obj, {
get(t, prop) {
if (keys.includes(prop)) return undefined;
return Reflect.get(t, prop);
},
ownKeys(t) {
return Reflect.ownKeys(t).filter(k => !keys.includes(k));
}
});
}
const user = hide({ name: "Alice", password: "123" }, ["password"]);
console.log(user.password); // undefined
console.log(Object.keys(user)); // ["name"]
12. 属性验证
javascript
const person = new Proxy({}, {
set(t, prop, value) {
if (prop === "age" && typeof value !== "number") {
throw new TypeError("年龄必须是数字");
}
return Reflect.set(t, prop, value);
}
});
person.age = 30; // ✅
person.age = "20"; // ❌ 抛错
13. 函数与构造函数参数验证
javascript
function sum(a, b) { return a + b; }
const safeSum = new Proxy(sum, {
apply(fn, thisArg, args) {
if (!args.every(n => typeof n === "number")) {
throw new TypeError("参数必须是数字");
}
return Reflect.apply(fn, thisArg, args);
}
});
console.log(safeSum(2, 3)); // 5
// console.log(safeSum(2, "x")); // 抛错
14. 数据绑定与可观察对象
javascript
function observable(obj, callback) {
return new Proxy(obj, {
set(t, prop, value) {
const result = Reflect.set(t, prop, value);
callback(prop, value);
return result;
}
});
}
const state = observable({ count: 0 }, (k, v) => {
console.log(`${k} 更新为 ${v}`);
});
state.count++; // 输出: count 更新为 1
总结
- Proxy 让我们可以拦截并自定义对象操作。
- Reflect 提供与代理捕获器对应的标准 API,确保操作符合规范。
- 常见用途包括:属性跟踪、隐藏敏感信息、数据验证、响应式编程。