Intercepta las operaciones fundamentales del lenguaje con las trampas de Proxy y delega limpiamente el comportamiento por defecto con Reflect.
Abrir esta lección en KodokonUn Proxy envuelve un objeto objetivo e intercepta las operaciones internas del lenguaje mediante trece trampas: get, set, has (el operador in), deleteProperty, ownKeys, apply, construct, etc. El objeto Reflect expone exactamente las mismas operaciones como funciones, con las mismas firmas que las trampas. Es la base de la reactividad de Vue 3, de los mocks dinámicos y de las API al estilo ORM.
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¿Por qué Reflect.get(obj, prop, receiver) en lugar de obj[prop]? Por el receiver. Si la propiedad es un descriptor de acceso (un getter), Reflect.get lo invoca con this apuntando al receiver - el proxy o un objeto que hereda de él - mientras que obj[prop] establecería this en el objetivo en bruto. Sin el receiver, los getters que leen this se rompen en cuanto un objeto hereda del 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 receiverLos casos de uso serios: validación al escribir, propiedades virtuales calculadas al vuelo, registro, control de acceso (ocultar claves con prefijo _ mediante has y ownKeys), u objetos con comportamiento por defecto. Ten en cuenta que Reflect.set devuelve un booleano en lugar de lanzar en modo estricto: tu trampa debe propagar ese valor de retorno.
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, ¿por qué preferir Reflect.get(target, prop, receiver) en lugar de target[prop]?this correcto (el receiver) en lugar del objetivo en brutotarget[prop] volvería a activar la trampa y causaría recursión infinita"id" in obj?get devuelve 42 para una propiedad del objetivo cuyo valor es 1, declarada no configurable y no escribible. ¿Qué ocurre?