DMarlon Studio

Lección 18 · 18 de 20

Proxy y Reflect · JavaScript

Intercepte lecturas, asignaciones y borrados en JavaScript con Proxy, y delegue en Reflect el comportamiento normal, con un usuario validado.

JavaScript · Proxy y Reflect en DMarlon Studio: el código del ejemplo y su salida en la terminal

Un `Proxy` envuelve un objeto y permite interceptar las operaciones que se hacen con él: leer, asignar, borrar, comprobar con in... Cada operación interceptada es una trampa (trap) del manejador. validated devuelve un proxy de { id: 7 } con tres trampas: set, get y deleteProperty.

La trampa set se ejecuta en cada asignación, como user.age = 34. Busca una regla para esa propiedad y, si el valor no la cumple, lanza un TypeError: así, user.age = 15 se rechaza sin tocar el objeto. Si el valor es válido, escribe un registro y hace la asignación de verdad.

`Reflect` ofrece las operaciones normales del lenguaje como funciones: Reflect.set, Reflect.get, Reflect.deleteProperty, Reflect.has... Dentro de una trampa, llamarlas es la forma correcta de «hacer lo de siempre» después de añadir su propia lógica, y devuelven true o false, como espera el proxy.

La trampa get devuelve un texto por defecto, (no phone), para las propiedades que no existen, y deleteProperty protege el id. Las operaciones sin trampa, como Reflect.ownKeys o el operador in, pasan directamente al objeto original. Con esta técnica se construyen validadores, registros de cambios y la reactividad de algunas librerías de interfaz.

Código · main.js

// A Proxy wraps an object and intercepts operations through "traps".function validated(target, rules) {  return new Proxy(target, {    set(obj, key, value, receiver) {      if (rules[key] && !rules[key](value)) {        throw new TypeError(`invalid ${key}: ${JSON.stringify(value)}`);      }      console.log(`set ${key} = ${JSON.stringify(value)}`);      return Reflect.set(obj, key, value, receiver); // the normal assignment    },    get(obj, key, receiver) {      return key in obj ? Reflect.get(obj, key, receiver) : `(no ${String(key)})`;    },    deleteProperty(obj, key) {      if (key === "id") throw new Error("id cannot be deleted");      return Reflect.deleteProperty(obj, key);    },  });}const user = validated({ id: 7 }, {  email: (v) => typeof v === "string" && v.includes("@"),  age: (v) => Number.isInteger(v) && v >= 18,});user.email = "ana@shop.es";user.age = 34;for (const attempt of [() => (user.age = 15), () => delete user.id]) {  try { attempt(); } catch (err) { console.log(`${err.name}: ${err.message}`); }}console.log(user.email, user.age, user.phone);console.log("Reflect.ownKeys:", Reflect.ownKeys(user).join(", "));console.log("Has email?", Reflect.has(user, "email"), "| has phone?", "phone" in user);

Salida · node main.js

set email = "ana@shop.es"
set age = 34
TypeError: invalid age: 15
Error: id cannot be deleted
ana@shop.es 34 (no phone)
Reflect.ownKeys: id, email, age
Has email? true | has phone? false

Para recordar

  • Un Proxy intercepta operaciones sobre un objeto mediante trampas.
  • set, get y deleteProperty son trampas habituales.
  • Reflect hace la operación normal y es el complemento natural de cada trampa.
  • Las operaciones sin trampa llegan sin cambios al objeto original.

Ejercicio

Añada una trampa has que oculte al operador in las propiedades que empiecen por _, y haga que email no se pueda cambiar una vez asignado.