L61 - Proxy y Reflect
🧠 Concepto
Section titled “🧠 Concepto”Proxy es un objeto que envuelve a otro (el target) y permite interceptar operaciones básicas como lectura, escritura, borrado y llamada de funciones. Es una forma de metaprogramación: el código puede “escuchar” y modificar el comportamiento en tiempo de ejecución.
Reflect es un objeto global con métodos que replican las operaciones internas de JavaScript. Siempre se usa junto con Proxy.
💻 Crear un Proxy
Section titled “💻 Crear un Proxy”const target = { mensaje: 'Hola' };
const handler = { get(obj, prop) { console.log(`Leyendo propiedad: ${prop}`); return obj[prop]; }};
const proxy = new Proxy(target, handler);
console.log(proxy.mensaje);// Leyendo propiedad: mensaje// Hola📝 Trampas (traps) más comunes
Section titled “📝 Trampas (traps) más comunes”get: interceptar lectura
Section titled “get: interceptar lectura”const usuario = { nombre: 'Ana', edad: 25 };
const proxyUsuario = new Proxy(usuario, { get(target, prop) { if (prop === 'edad') { const edad = target[prop]; return `Tiene ${edad} años`; } if (!(prop in target)) { return `Propiedad "${prop}" no existe`; } return target[prop]; }});
console.log(proxyUsuario.nombre); // Anaconsole.log(proxyUsuario.edad); // Tiene 25 añosconsole.log(proxyUsuario.email); // Propiedad "email" no existeset: interceptar escritura (validación)
Section titled “set: interceptar escritura (validación)”const producto = { precio: 100 };
const proxyProducto = new Proxy(producto, { set(target, prop, value) { if (prop === 'precio') { if (typeof value !== 'number' || value <= 0) { throw new Error('El precio debe ser un número positivo'); } } console.log(`Cambiando ${prop} a ${value}`); target[prop] = value; return true; // indica que la operación fue exitosa }});
proxyProducto.precio = 150; // ✓// proxyProducto.precio = -5; // Error: El precio debe ser un número positivohas: interceptar operador in
Section titled “has: interceptar operador in”const rango = new Proxy({ min: 10, max: 50 }, { has(target, prop) { const num = Number(prop); return num >= target.min && num <= target.max; }});
console.log(25 in rango); // trueconsole.log(5 in rango); // falseconsole.log(100 in rango); // falsedeleteProperty: interceptar delete
Section titled “deleteProperty: interceptar delete”const config = { apiKey: 'abc123', publica: 'valorPublico'};
const proxyConfig = new Proxy(config, { deleteProperty(target, prop) { if (prop === 'apiKey') { throw new Error('No puedes eliminar la API key'); } console.log(`Eliminando ${prop}`); return delete target[prop]; }});
// delete proxyConfig.apiKey; // Errordelete proxyConfig.publica; // ✓💻 Reflect
Section titled “💻 Reflect”Reflect proporciona los mismos métodos que los traps de Proxy, pero como funciones independientes. Se usa dentro de los handlers para delegar la operación al target original:
const usuario = { nombre: 'Ana' };
const proxyReflect = new Proxy(usuario, { get(target, prop, receiver) { console.log(`Accediendo a ${prop}`); return Reflect.get(target, prop, receiver); }, set(target, prop, value, receiver) { console.log(`Asignando ${prop} = ${value}`); return Reflect.set(target, prop, value, receiver); }});
proxyReflect.nombre = 'Carlos';console.log(proxyReflect.nombre);Siempre usa Reflect dentro de los traps en lugar de manipular target directamente. Garantiza el comportamiento correcto (por ejemplo, mantener el binding de this en getters).
🎯 Casos de uso prácticos
Section titled “🎯 Casos de uso prácticos”1. Valores por defecto
Section titled “1. Valores por defecto”function conValoresPorDefecto(target, defaults) { return new Proxy(target, { get(target, prop) { if (prop in target) { return Reflect.get(target, prop); } return defaults[prop]; } });}
const config = conValoresPorDefecto({}, { puerto: 3000, host: 'localhost', debug: false});
console.log(config.puerto); // 3000 (valor por defecto)config.puerto = 5000;console.log(config.puerto); // 5000 (sobrescribe)2. Logging automático
Section titled “2. Logging automático”function conLogging(target) { return new Proxy(target, { get(target, prop, receiver) { const value = Reflect.get(target, prop, receiver); if (typeof value === 'function') { return function(...args) { console.log(`Llamando a ${prop}(${args.join(', ')})`); return value.apply(this, args); }; } return value; } });}
const calculadora = conLogging({ sumar: (a, b) => a + b, restar: (a, b) => a - b});
calculadora.sumar(3, 4); // Llamando a sumar(3, 4) → 73. Propiedades privadas (simuladas)
Section titled “3. Propiedades privadas (simuladas)”function privado(target, propiedadesPrivadas) { return new Proxy(target, { get(target, prop) { if (propiedadesPrivadas.includes(prop)) { throw new Error(`"${prop}" es privada`); } return Reflect.get(target, prop); }, set(target, prop, value) { if (propiedadesPrivadas.includes(prop)) { throw new Error(`"${prop}" es privada`); } return Reflect.set(target, prop, value); } });}
const user = privado({ nombre: 'Ana', _password: 'secreta' }, ['_password']);console.log(user.nombre); // Ana// console.log(user._password); // Error📝 Proxy vs getters/setters nativos
Section titled “📝 Proxy vs getters/setters nativos”| Proxy | Getters/setters |
|---|---|
| Intercepta cualquier operación | Solo lectura/escritura |
| No requiere modificar el target | Hay que definirlos en cada propiedad |
| Útil para wrapping genérico | Útil para objetos conocidos |
| Más lento (overhead) | Sin overhead |
🎯 Regla de oro: usa Proxy cuando necesites lógica transversal (logging, validación, valores por defecto) que aplique a múltiples propiedades. Para casos puntuales, usa getters/setters.