Traps get y set: Interceptando Lecturas y Escrituras en Proxy
Domina los traps get y set de Proxy para controlar el acceso a propiedades, implementar validación, reactividad y propiedades virtuales en JavaScript.
TL;DR - Resumen rápido
- El trap get intercepta todas las operaciones de lectura de propiedades (obj.prop y obj['prop'])
- El trap set intercepta todas las operaciones de escritura de propiedades (obj.prop = valor)
- Ambos traps reciben el receiver como cuarto parámetro, que es el objeto o Proxy que inició la operación
- Usa Reflect.get y Reflect.set dentro de los traps para realizar las operaciones originales de forma consistente
- Los traps get y set son la base para implementar reactividad, validación, logging y propiedades computadas
Introducción a Traps get y set
Los traps get y set son los dos traps más utilizados en Proxy porque interceptan las operaciones más comunes sobre objetos: la lectura y escritura de propiedades. Cada vez que accedes a una propiedad usando notación de punto (obj.propiedad) o notación de corchetes (obj['propiedad']), se ejecuta el trap get. Similarmente, cada vez que asignas un valor a una propiedad, se ejecuta el trap set.
Estos traps te permiten implementar patrones de diseño avanzados sin modificar el objeto original. Puedes agregar validación automática, implementar reactividad similar a frameworks modernos, crear propiedades virtuales que no existen en el objeto original, y mucho más.
- Validación de tipos y valores antes de permitir la asignación
- Implementación de propiedades computadas que se calculan dinámicamente
- Logging y debugging de todas las operaciones de acceso a propiedades
- Control de acceso basado en permisos o roles de usuario
- Implementación de reactividad para actualizaciones automáticas de UI
Diferencia con Getters/Setters
Los traps get y set de Proxy son más potentes que los getters y setters tradicionales de ES5 porque interceptan TODAS las operaciones de lectura y escritura, incluyendo propiedades que no existen en el objeto original. Los getters/setters solo funcionan para propiedades específicas que hayas definido previamente.
Trap get: Interceptando Lecturas
El trap get se ejecuta cada vez que lees una propiedad de un Proxy. Recibe tres parámetros: el target (el objeto original), la propiedad que se está accediendo (como string o Symbol), y el receiver (el objeto o Proxy que inició la operación). El receiver es importante porque permite que el trap funcione correctamente con Proxies anidados y herencia.
Dentro del trap get, puedes implementar cualquier lógica antes de devolver el valor. Puedes validar que la propiedad exista, transformar el valor, implementar propiedades virtuales, o incluso devolver valores diferentes según el contexto de la llamada.
Uso Básico del Trap get
Este ejemplo muestra cómo usar el trap get para interceptar todas las lecturas de propiedades y agregar logging. El trap recibe la propiedad y usa Reflect.get para obtener el valor del target original.
El trap get intercepta todas las lecturas de propiedades, incluyendo las que usan notación de corchetes. En este ejemplo, simplemente registramos qué propiedad se está accediendo y luego usamos Reflect.get para obtener el valor del target original.
Propiedades Virtuales con get
Una de las características más poderosas del trap get es la capacidad de crear propiedades virtuales: propiedades que no existen en el objeto original pero que se comportan como si existieran. Esto es útil para implementar propiedades computadas, valores derivados o propiedades que se calculan dinámicamente.
Este ejemplo muestra cómo crear propiedades virtuales que no existen en el objeto original. Las propiedades nombreCompleto y edadEnDias se calculan dinámicamente cada vez que se acceden, mientras que las propiedades reales (nombre, apellido, edad) se obtienen del target original.
Mejor Práctica: Nombres de Propiedades Virtuales
Usa un prefijo o sufijo para distinguir las propiedades virtuales de las propiedades reales. Por ejemplo, usa getNombreCompleto() para métodos o nombreCompleto para propiedades computadas. Esto hace el código más claro y evita confusiones.
Trap set: Interceptando Escrituras
El trap set se ejecuta cada vez que asignas un valor a una propiedad de un Proxy. Recibe cuatro parámetros: el target, la propiedad que se está modificando, el nuevo valor, y el receiver. El trap debe devolver true si la asignación fue exitosa, o false si falló. Si lanzas un error dentro del trap, la asignación falla y el error se propaga.
El trap set es ideal para implementar validación de datos. Puedes verificar que el valor cumpla ciertas condiciones antes de permitir la asignación. Si la validación falla, puedes lanzar un error específico o devolver false para indicar que la operación no fue exitosa.
Validación de Datos con set
El trap set es ideal para validar datos antes de permitir la asignación. Puedes verificar formatos, tipos o rangos de valores, y lanzar errores descriptivos cuando la validación falle.
Este ejemplo valida el formato de email usando una expresión regular. Si el email no cumple el formato esperado, el trap lanza un error antes de que el valor se asigne al objeto. Las asignaciones válidas se delegan a Reflect.set para que funcionen normalmente.
Inmutabilidad con set
El trap set también permite crear objetos inmutables que rechacen cualquier intento de modificación. Esto es útil para proteger configuraciones o constantes que no deben cambiar durante la ejecución.
El trap set lanza un error para cualquier intento de modificación, garantizando que el objeto permanezca inalterado. Las lecturas funcionan normalmente, solo las escrituras están bloqueadas. Esto es más flexible que Object.freeze() porque puedes implementar lógica condicional si fuera necesario.
Alternativa: Object.freeze()
Para inmutabilidad simple, Object.freeze(obj) congela el objeto y previene modificaciones. Sin embargo, Proxy ofrece más flexibilidad porque puedes personalizar el mensaje de error o implementar reglas más complejas sobre qué propiedades son inmutables.
Propiedades Virtuales Avanzadas
Las propiedades virtuales pueden ser más complejas y calcular valores derivados de múltiples propiedades. Combinando get y set, puedes crear propiedades computadas de solo lectura que se recalculan automáticamente cuando cambian sus dependencias.
Este ejemplo calcula subtotal y total dinámicamente basándose en precio, cantidad y tasa de IVA. Las propiedades virtuales se recalculan automáticamente cuando cambias precio o cantidad. El trap set previene que intentes modificar directamente las propiedades calculadas, lanzando un error si lo intentas.
Implementando Reactividad
La reactividad es un patrón donde las actualizaciones de datos desencadenan automáticamente otras acciones. El trap set puede detectar cambios en propiedades y ejecutar callbacks, permitiéndote actualizar la UI, recalcular valores o enviar notificaciones automáticamente.
Este ejemplo crea un sistema de reactividad simple. Cada vez que modificas una propiedad, el trap set compara el valor anterior con el nuevo, y si son diferentes, ejecuta el callback con la información del cambio. Si asignas el mismo valor, el callback no se ejecuta, optimizando el rendimiento.
Aplicaciones de Reactividad
Este patrón es la base de muchos sistemas de gestión de estado en JavaScript. Puedes usarlo para sincronizar datos con LocalStorage, actualizar elementos del DOM automáticamente, o implementar observadores de cambios para debugging y logging.
Errores Comunes
Al trabajar con los traps get y set, hay varios errores comunes que debes evitar. Estos errores pueden causar comportamientos inesperados, bugs difíciles de debuggear o problemas de rendimiento.
Error: Olvidar Usar Reflect
Un error común es acceder directamente a target[prop] en lugar de usar Reflect. Aunque funciona en casos simples, puede causar problemas con getters/setters, herencia y Proxies anidados.
Ambos enfoques funcionan para casos simples, pero Reflect garantiza comportamiento consistente con el lenguaje. Reflect.get y Reflect.set manejan correctamente la cadena de prototipos, getters/setters personalizados y el parámetro receiver para Proxies anidados. Siempre usa Reflect para evitar bugs sutiles.
Por Qué Usar Reflect
Reflect.get y Reflect.set son equivalentes funcionales a las operaciones nativas de JavaScript, pero diseñadas específicamente para usarse dentro de traps de Proxy. Garantizan que el comportamiento sea idéntico al de las operaciones normales, especialmente con getters/setters y herencia.
Error: Bucle Infinito
Acceder al receiver dentro de un trap crea un bucle infinito. Como receiver es el Proxy mismo, acceder a receiver[prop] vuelve a ejecutar el trap, que vuelve a acceder al receiver, causando "Maximum call stack size exceeded".
El error ocurre porque receiver es el Proxy, no el target. Cuando el trap get accede a receiver[prop], ejecuta el trap get nuevamente, causando recursión infinita. La solución es usar Reflect.get(target, prop, receiver) que accede al target pero mantiene el contexto del receiver para la cadena de prototipos.
Error: No Devolver true en set
El trap set debe devolver true si la operación fue exitosa. Si devuelves undefined (o no devuelves nada), en modo estricto JavaScript lanza un TypeError indicando que el trap devolvió un valor falsy.
En modo estricto, si el trap set no devuelve true, obtienes un TypeError. La solución es devolver el resultado de Reflect.set, que siempre devuelve true cuando la asignación tiene éxito. Esto garantiza que el comportamiento sea consistente con las asignaciones normales de JavaScript.
Resumen: Traps get y set
Conceptos principales:
- •El trap get intercepta todas las lecturas de propiedades (obj.prop y obj['prop'])
- •El trap set intercepta todas las escrituras de propiedades (obj.prop = valor)
- •Ambos traps reciben el receiver como parámetro para manejar Proxies anidados correctamente
- •Las propiedades virtuales se calculan dinámicamente y no existen en el objeto original
- •La reactividad se implementa detectando cambios en propiedades y ejecutando observadores
Mejores prácticas:
- •Usa siempre Reflect.get y Reflect.set dentro de los traps para comportamiento consistente
- •Implementa validación en el trap set para prevenir asignaciones de valores inválidos
- •Usa prefijos para distinguir propiedades virtuales de propiedades reales
- •Evita acceder al receiver dentro de los traps para prevenir bucles infinitos
- •Asegúrate de devolver true en el trap set cuando la operación sea exitosa