Intercept the language's fundamental operations with Proxy traps and cleanly delegate default behavior with Reflect.
Open this lesson in KodokonA Proxy wraps a target object and intercepts the language's internal operations through thirteen traps: get, set, has (the in operator), deleteProperty, ownKeys, apply, construct, and so on. The Reflect object exposes exactly the same operations as functions, with the same signatures as the traps. It is the foundation of Vue 3's reactivity, of dynamic mocks, and of ORM-style APIs.
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 nameWhy Reflect.get(obj, prop, receiver) rather than obj[prop]? Because of the receiver. If the property is an accessor (a getter), Reflect.get invokes it with this pointing at the receiver - the proxy or an object inheriting from it - whereas obj[prop] would set this to the raw target. Without the receiver, getters that read this break as soon as an object inherits from the proxy.
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 receiverThe serious use cases: validation on write, virtual properties computed on the fly, logging, access control (hiding keys prefixed with _ via has and ownKeys), or objects with default behavior. Note that Reflect.set returns a boolean instead of throwing in strict mode: your trap must propagate that return value.
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 trap, why prefer Reflect.get(target, prop, receiver) over target[prop]?this (the receiver) rather than the raw targettarget[prop] would re-trigger the trap and cause infinite recursion"id" in obj?get trap returns 42 for a target property whose value is 1, declared non-configurable and non-writable. What happens?