Introducción a Proxy: Interceptación de Operaciones en JavaScript
Aprende qué son los Proxies y cómo permiten interceptar y personalizar operaciones fundamentales sobre objetos para crear patrones avanzados de metaprogramación.
TL;DR - Resumen rápido
- Proxy es un objeto que envuelve a otro para interceptar operaciones como lectura, escritura y eliminación de propiedades
- Usa 'traps' (funciones interceptoras) para personalizar el comportamiento de operaciones estándar de JavaScript
- La sintaxis básica es new Proxy(target, handler) donde target es el objeto original y handler contiene los traps
- Proxy permite implementar patrones como validación, logging, reactividad y control de acceso sin modificar el objeto original
- Los Proxies no son transparentes para todas las operaciones: typeof, instanceof y algunos operadores pueden comportarse diferente
Introducción a Proxy
La metaprogramación en JavaScript te permite escribir código que manipula otros programas o a sí mismo. Los Proxies, introducidos en ES6 (ECMAScript 2015), son una de las herramientas más poderosas de metaprogramación disponibles en JavaScript moderno. Un Proxy actúa como un intermediario entre tu código y un objeto, permitiéndote interceptar y personalizar operaciones fundamentales como acceder a propiedades, asignar valores, llamar funciones y mucho más.
Antes de Proxy, lograr este tipo de comportamiento requería técnicas complejas como sobrescribir métodos nativos, usar getters/setters extensivamente o manipular la cadena de prototipos. Proxy ofrece una solución más limpia, flexible y estandarizada que se integra de forma nativa con el lenguaje.
- Validación automática de datos antes de asignarlos a propiedades
- Logging y debugging de todas las operaciones sobre un objeto
- Implementación de reactividad similar a frameworks modernos
- Control de acceso y permisos sobre propiedades sensibles
- Creación de objetos virtuales que no existen en memoria
Contexto Histórico
Proxy fue introducido en ES6 (2015) junto con otras características avanzadas como Symbols, Iterators y Generators. Fue diseñado para llenar el vacío entre JavaScript como lenguaje de scripting y las necesidades de frameworks modernos que requieren capacidades de metaprogramación más sofisticadas.
¿Qué es un Proxy?
Un Proxy es un objeto que envuelve a otro objeto (llamado target) e intercepta operaciones que se realizan sobre él. Esta interceptación se logra a través de funciones especiales llamadas "traps" que se definen en un objeto handler. Cuando realizas una operación sobre el Proxy, JavaScript primero verifica si existe un trap correspondiente en el handler. Si existe, ejecuta el trap; si no, la operación se delega al objeto original (target).
Esta arquitectura te permite modificar el comportamiento predeterminado de JavaScript sin alterar el objeto original. El target permanece inalterable, mientras que el Proxy puede implementar cualquier lógica personalizada que necesites antes, durante o después de cada operación.
Terminología Clave
Target: El objeto original que está siendo envuelto por el Proxy. Puede ser cualquier objeto JavaScript, incluyendo arrays, funciones e incluso otros Proxies.
Handler: Un objeto que contiene las funciones (traps) que definen cómo se interceptan las operaciones.
Trap: Una función que intercepta una operación específica (como get, set, has, deleteProperty, etc.).
Reflect: Un objeto integrado que proporciona métodos para realizar las operaciones originales de JavaScript, usado comúnmente dentro de los traps.
Sintaxis Básica
La sintaxis para crear un Proxy es straightforward: new Proxy(target, handler). El primer argumento es el objeto que deseas envolver (target), y el segundo es un objeto que contiene los traps (handler). El handler puede estar vacío, en cuyo caso el Proxy se comporta exactamente como el objeto original, o puede contener uno o más traps para interceptar operaciones específicas.
Handler y Traps
El handler es un objeto plano donde cada propiedad es una función que intercepta una operación específica. Estas funciones se llaman "traps" porque "atrapan" las operaciones antes de que lleguen al objeto original. Los traps disponibles cubren prácticamente todas las operaciones que puedes realizar sobre un objeto en JavaScript.
Este ejemplo muestra la estructura básica de un Proxy. El handler está vacío, por lo que el Proxy se comporta exactamente como el objeto original. Sin embargo, la verdadera potencia de Proxy emerge cuando agregamos traps al handler para interceptar operaciones específicas.
- <strong>get:</strong> Intercepta la lectura de propiedades (obj.propiedad o obj['propiedad'])
- <strong>set:</strong> Intercepta la escritura de propiedades (obj.propiedad = valor)
- <strong>has:</strong> Intercepta el operador in ('propiedad' in obj)
- <strong>deleteProperty:</strong> Intercepta el operador delete (delete obj.propiedad)
- <strong>apply:</strong> Intercepta llamadas a funciones (proxy())
- <strong>construct:</strong> Intercepta el operador new (new Proxy())
Ejemplo Práctico
Para entender mejor cómo funcionan los Proxies, veamos un ejemplo práctico que implementa validación de datos. En este caso, crearemos un Proxy que valida que los valores asignados a propiedades cumplan ciertas reglas antes de permitir la asignación.
Validación con Proxy
El trap set se ejecuta cada vez que intentas asignar un valor a una propiedad. Puedes usar este trap para validar el valor antes de permitir la asignación. Si la validación falla, puedes lanzar un error; si tiene éxito, usas Reflect.set para realizar la asignación al objeto original.
Este ejemplo muestra cómo validar que la edad sea un número entre 0 y 150. El trap intercepta todas las asignaciones a la propiedad edad, verifica que cumplan las reglas, y solo entonces permite que el valor se asigne al objeto original usando Reflect.set. Los intentos de asignar valores inválidos lanzan un error antes de que se modifique el objeto.
Mejor Práctica: Usa Reflect en los Traps
Dentro de los traps, usa siempre los métodos de Reflect para realizar la operación original. Reflect.set(target, prop, value, receiver) garantiza que el comportamiento sea consistente con las operaciones nativas de JavaScript, especialmente cuando hay getters/setters o Proxies anidados involucrados.
Logging con Proxy
Otro uso común de Proxy es el logging de operaciones. Los traps get y set te permiten interceptar todas las lecturas y escrituras sobre un objeto, registrándolas automáticamente sin necesidad de modificar el código que usa el objeto.
Este ejemplo registra cada lectura y escritura con un mensaje descriptivo en consola. Cada vez que accedes a una propiedad, el trap get se ejecuta primero, registra la operación, y luego retorna el valor usando Reflect.get. Lo mismo ocurre con las asignaciones a través del trap set. Este patrón es invaluable para debugging y análisis de comportamiento de objetos complejos.
Limitaciones de Proxy
Aunque los Proxies son muy poderosos, tienen limitaciones importantes que debes conocer. No todas las operaciones de JavaScript pueden ser interceptadas, y algunos objetos integrados tienen "internal slots" que no son accesibles a través de Proxies, lo que puede causar errores inesperados.
Este ejemplo muestra tres limitaciones clave: el operador typeof siempre devuelve 'object' para un Proxy (incluso si el target es una función), los operadores aritméticos (+, -, *, /) no pueden ser interceptados, y objetos como Map o Set tienen internal slots que el Proxy no puede replicar, causando errores al llamar sus métodos.
Advertencia: Proxies e Internal Slots
Objetos integrados como Map, Set, Date y WeakMap usan internal slots (como [[MapData]]) que no son accesibles a través de Proxies. Si intentas crear un Proxy de estos objetos y llamar sus métodos, obtendrás errores. Para estos casos, necesitas un approach diferente, como envolver el objeto en lugar de proxificarlo directamente.
Resumen: Introducción a Proxy
Conceptos principales:
- •Proxy envuelve objetos para interceptar operaciones fundamentales sin modificar el objeto original
- •La sintaxis es new Proxy(target, handler) donde handler contiene las funciones trap
- •Los traps interceptan operaciones específicas: get (lectura), set (escritura), has, deleteProperty, apply, construct
- •Reflect API proporciona métodos para ejecutar las operaciones originales dentro de los traps
- •Proxy permite implementar validación, logging, reactividad y control de acceso de forma transparente
Mejores prácticas:
- •Usa siempre Reflect dentro de los traps para mantener consistencia con el comportamiento nativo
- •Valida datos en el trap set antes de permitir asignaciones al objeto original
- •Implementa logging con get y set para debugging y auditoría de operaciones
- •Ten en cuenta que typeof, operadores aritméticos e internal slots no son interceptables
- •Evita proxificar directamente objetos integrados como Map, Set o Date debido a internal slots