Proxy y Reflect
Metaprogramación con objetos Proxy y la API Reflect
Introducción a la Metaprogramación
La Metaprogramación es escribir código que manipula o intercepta otro código en tiempo de ejecución.
Las APIs Proxy y Reflect de JavaScript permiten patrones poderosos de metaprogramación
como validación, logging, objetos virtuales y sistemas reactivos.
Conceptos Clave
- Proxy — Envuelve un objeto para interceptar y personalizar operaciones
- Handler — Objeto que contiene métodos trampa
- Trampa (Trap) — Método que intercepta una operación (get, set, etc.)
- Reflect — API que refleja trampas de Proxy con comportamientos predeterminados
Proxy Básico
// Sintaxis: new Proxy(objetivo, manejador)
const objetivo = {
nombre: "Alice",
edad: 30
};
const manejador = {
get(objetivo, propiedad, receptor) {
console.log(`Obteniendo ${propiedad}`);
return objetivo[propiedad];
},
set(objetivo, propiedad, valor, receptor) {
console.log(`Estableciendo ${propiedad} a ${valor}`);
objetivo[propiedad] = valor;
return true; // Debe retornar true para éxito
}
};
const proxy = new Proxy(objetivo, manejador);
proxy.nombre; // Registra: "Obteniendo nombre", retorna "Alice"
proxy.edad = 31; // Registra: "Estableciendo edad a 31"
console.log(proxy.edad); // 31
Trampas Comunes de Proxy
| Trampa | Intercepta |
|---|---|
get(target, prop) |
Acceso a propiedad: obj.prop |
set(target, prop, value) |
Asignación de propiedad: obj.prop = valor |
has(target, prop) |
Operador in |
deleteProperty(target, prop) |
Operador delete |
apply(target, thisArg, args) |
Llamadas a funciones |
construct(target, args) |
Operador new |
Proxy de Validación
function crearObjetoValidado(esquema) {
return new Proxy({}, {
set(objetivo, propiedad, valor) {
const validador = esquema[propiedad];
if (!validador) {
throw new Error(`Propiedad desconocida: ${propiedad}`);
}
if (!validador(valor)) {
throw new Error(`Valor inválido para ${propiedad}: ${valor}`);
}
objetivo[propiedad] = valor;
return true;
}
});
}
const usuario = crearObjetoValidado({
nombre: (v) => typeof v === "string" && v.length > 0,
edad: (v) => typeof v === "number" && v >= 0 && v < 150,
email: (v) => /^[^@]+@[^@]+\.[^@]+$/.test(v)
});
usuario.nombre = "Alice"; // ✓ OK
usuario.edad = 30; // ✓ OK
usuario.edad = -5; // ✗ Error: Valor inválido para edad
usuario.foo = "bar"; // ✗ Error: Propiedad desconocida: foo
Valores Predeterminados y Propiedades Virtuales
// Crear automáticamente propiedades faltantes
const conDefectos = new Proxy({}, {
get(objetivo, propiedad) {
if (!(propiedad in objetivo)) {
objetivo[propiedad] = 0; // Valor predeterminado
}
return objetivo[propiedad];
}
});
console.log(conDefectos.cuenta); // 0 (auto-creado)
conDefectos.cuenta++;
console.log(conDefectos.cuenta); // 1
// Propiedades virtuales (computadas)
const usuario = {
nombre: "Juan",
apellido: "Pérez"
};
const proxyUsuario = new Proxy(usuario, {
get(objetivo, propiedad) {
if (propiedad === "nombreCompleto") {
return `${objetivo.nombre} ${objetivo.apellido}`;
}
return objetivo[propiedad];
}
});
console.log(proxyUsuario.nombreCompleto); // "Juan Pérez"
La API Reflect
Reflect proporciona implementaciones predeterminadas para trampas de Proxy:
// Reflect refleja trampas de Proxy
const obj = { x: 1, y: 2 };
// En lugar de:
obj.x; // Get
obj.x = 10; // Set
"x" in obj; // Has
delete obj.x; // Delete
// Usa Reflect:
Reflect.get(obj, "x"); // Get
Reflect.set(obj, "x", 10); // Set
Reflect.has(obj, "x"); // Has
Reflect.deleteProperty(obj, "x"); // Delete
// Útil en manejadores Proxy para llamar comportamiento predeterminado
const proxyLogging = new Proxy(obj, {
get(objetivo, propiedad, receptor) {
console.log(`Accediendo a ${propiedad}`);
return Reflect.get(objetivo, propiedad, receptor); // Comportamiento predeterminado
},
set(objetivo, propiedad, valor, receptor) {
console.log(`Estableciendo ${propiedad} = ${valor}`);
return Reflect.set(objetivo, propiedad, valor, receptor);
}
});
Proxy Reactivo (Estilo Vue)
function reactivo(obj, alCambiar) {
return new Proxy(obj, {
set(objetivo, propiedad, valor, receptor) {
const valorAntiguo = objetivo[propiedad];
const resultado = Reflect.set(objetivo, propiedad, valor, receptor);
if (valorAntiguo !== valor) {
alCambiar(propiedad, valor, valorAntiguo);
}
return resultado;
}
});
}
const estado = reactivo({ cuenta: 0 }, (prop, nuevoVal, viejoVal) => {
console.log(`${prop} cambió: ${viejoVal} → ${nuevoVal}`);
// Re-renderizar UI, etc.
});
estado.cuenta = 1; // "cuenta cambió: 0 → 1"
estado.cuenta = 2; // "cuenta cambió: 1 → 2"
Proxy de Función
// Interceptar llamadas a funciones
function crearFuncionConLog(fn) {
return new Proxy(fn, {
apply(objetivo, thisArg, args) {
console.log(`Llamando a ${fn.name} con:`, args);
const resultado = Reflect.apply(objetivo, thisArg, args);
console.log(`Resultado:`, resultado);
return resultado;
}
});
}
const sumar = (a, b) => a + b;
const sumarConLog = crearFuncionConLog(sumar);
sumarConLog(2, 3);
// Llamando a sumar con: [2, 3]
// Resultado: 5
// Interceptar llamadas a constructores
class Usuario {
constructor(nombre) {
this.nombre = nombre;
}
}
const UsuarioRastreado = new Proxy(Usuario, {
construct(objetivo, args) {
console.log("Creando usuario:", args[0]);
return Reflect.construct(objetivo, args);
}
});
new UsuarioRastreado("Alice"); // "Creando usuario: Alice"
Proxies Revocables
// Crear un proxy que puede ser deshabilitado
const { proxy, revoke } = Proxy.revocable(
{ secreto: "password123" },
{
get(objetivo, prop) {
return objetivo[prop];
}
}
);
console.log(proxy.secreto); // "password123"
// Revocar acceso
revoke();
console.log(proxy.secreto); // TypeError: No se puede realizar 'get' en un proxy revocado
⚠️ Consideraciones
- Rendimiento: Los proxies agregan sobrecarga; evítalos en rutas críticas
- Identidad: proxy !== objetivo, lo cual puede causar problemas
- Built-ins: Algunos objetos (Map, Set) necesitan manejo especial
- Invariantes: Las trampas deben respetar las invariantes de JavaScript
💡 Puntos Clave
- • Proxy intercepta operaciones fundamentales en objetos
- • Usa trampas (get, set, has, etc.) para personalizar comportamiento
- • Reflect proporciona implementaciones predeterminadas para operaciones de trampa
- • Excelente para validación, logging, sistemas reactivos y control de acceso
- • Los proxies revocables permiten deshabilitar acceso a datos
Práctica y consejos de seguridad
- • Construye un proxy de logging alrededor de un objeto de servicio; inspecciona cómo
receiverafecta propiedades heredadas. - • Agrega validación en una trampa
sety compara comportamiento con setters deObject.defineProperty. - • Crea un proxy revocable para secretos y afirma que el acceso lanza una vez revocado.
- • Mide código de ruta crítica con y sin proxies para decidir si mantenerlos en producción.