用 Proxy 陷阱拦截语言的基本操作,并用 Reflect 干净地委托默认行为。
在 Kodokon 中打开本课Proxy 会包裹一个目标对象,并通过十三个陷阱拦截语言的内部操作:get、set、has(in 运算符)、deleteProperty、ownKeys、apply、construct 等等。Reflect 对象以函数的形式暴露了完全相同的这些操作,其签名与这些陷阱一致。它是 Vue 3 响应式系统、动态 mock 以及 ORM 风格 API 的基础。
const target = { name: "Ada" };
const traced = new Proxy(target, {
get(obj, prop, receiver) {
console.log("get " + String(prop));
return Reflect.get(obj, prop, receiver);
}
});
traced.name; // get name为什么用 Reflect.get(obj, prop, receiver) 而不是 obj[prop]?因为接收者(receiver)。如果该属性是一个访问器(getter),Reflect.get 会以 this 指向接收者——也就是代理本身或某个继承自它的对象——来调用它,而 obj[prop] 会把 this 设为原始目标。如果没有接收者,一旦某个对象继承自代理,读取 this 的 getter 就会失效。
const base = {
get label() {
return this.name;
}
};
const proxy = new Proxy(base, {
get(target, prop, receiver) {
return Reflect.get(target, prop, receiver);
}
});
const child = Object.create(proxy);
child.name = "Ada";
console.log(child.label); // "Ada" thanks to the receiver真正严肃的用例:写入时的校验、即时计算的虚拟属性、日志记录、访问控制(通过 has 和 ownKeys 隐藏以 _ 为前缀的键),或者带有默认行为的对象。请注意,Reflect.set 在严格模式下返回一个布尔值而不是抛出异常:你的陷阱必须把这个返回值传播出去。
function validated(target) {
return new Proxy(target, {
set(obj, prop, value, receiver) {
if (prop === "age" && !Number.isInteger(value)) {
throw new TypeError("age must be an integer");
}
return Reflect.set(obj, prop, value, receiver);
}
});
}
const user = validated({ age: 30 });
user.age = 31; // ok
// user.age = "old"; // TypeErrorget 陷阱中,为什么要优先使用 Reflect.get(target, prop, receiver) 而不是 target[prop]?this(接收者)运行,而不是以原始目标运行target[prop] 会重新触发陷阱并导致无限递归"id" in obj?get 陷阱对某个目标属性返回 42,而该属性的值为 1,被声明为不可配置且不可写。会发生什么?