Command Palette

Search for a command to run...

Traps apply y construct: Interceptando Llamadas a Funciones y Constructores en Proxy

Domina los traps apply y construct de Proxy para interceptar llamadas a funciones y el operador new, implementando memoización, validación de argumentos y control de instanciación.

Lectura: 16 min
Nivel: Intermedio

TL;DR - Resumen rápido

  • El trap apply intercepta llamadas a funciones (funcion())
  • El trap construct intercepta el operador new (new Constructor())
  • Ambos traps permiten implementar memoización, logging y validación de argumentos
  • apply recibe target, thisArg y argumentsList como parámetros
  • construct recibe target, argumentsList y newTarget como parámetros

Introducción a Traps apply y construct

Los traps apply y construct son especiales porque funcionan con funciones y constructores, no con objetos regulares. El trap apply se ejecuta cuando llamas a una función usando notación de paréntesis (funcion()), mientras que el trap construct se ejecuta cuando usas el operador new para crear una instancia (new Constructor()).

Estos traps son extremadamente útiles para implementar patrones avanzados como memoización (caching de resultados), logging de llamadas a funciones, validación de argumentos, control de instanciación y más. A diferencia de los traps que trabajan con objetos, apply y construct te permiten interceptar el comportamiento fundamental de funciones y constructores.

  • Implementar memoización para funciones costosas y mejorar el rendimiento
  • Validar argumentos antes de ejecutar funciones o constructores
  • Implementar logging y debugging de todas las llamadas a funciones
  • Controlar la instanciación de clases (patrón Singleton, Factory)
  • Implementar decoradores y middleware para funciones

Diferencia con Function.prototype

Los traps apply y construct de Proxy son más potentes que modificar Function.prototype.apply o Function.prototype.call porque interceptan TODAS las llamadas a la función, incluyendo las que usan notación directa (funcion()). Además, Proxy no modifica la función original, lo que lo hace más seguro y menos intrusivo.

Trap apply: Interceptando Llamadas a Funciones

El trap apply se ejecuta cada vez que llamas a una función usando notación de paréntesis. Recibe tres parámetros: el target (la función original), el thisArg (el valor de this en la llamada) y argumentsList (un array-like con los argumentos de la función). El trap debe devolver el resultado de la función.

Dentro del trap apply, puedes implementar cualquier lógica antes de llamar a la función. Puedes validar los argumentos, implementar memoización, agregar logging, o incluso modificar los argumentos antes de pasarlos a la función original.

Uso Básico del Trap apply

Este ejemplo muestra cómo usar el trap apply para interceptar todas las llamadas a una función y agregar logging. El trap recibe la función, el valor de this y los argumentos, y usa Reflect.apply para llamar a la función original.

apply-basico.js
Loading code...

El trap apply intercepta todas las llamadas a la función. En este ejemplo, registramos qué función se está llamando, los argumentos que se pasan, y el resultado que devuelve. Luego usamos Reflect.apply para llamar a la función original.

Memoización con apply

La memoización es una técnica de optimización que cachea los resultados de funciones costosas para evitar recalcularlos. El trap apply es perfecto para implementar memoización porque intercepta todas las llamadas a la función y puede verificar si los argumentos ya fueron procesados anteriormente.

memoizacion-con-apply.js
Loading code...

Este ejemplo muestra cómo implementar memoización usando el trap apply. La primera vez que llamas a la función con ciertos argumentos, se calcula el resultado y se cachea. Las llamadas posteriores con los mismos argumentos devuelven el resultado cacheado sin recalcularlo.

Mejor Práctica: Serialización de Argumentos

Para memoizar funciones con argumentos complejos, usa JSON.stringify() para crear una clave única. Ten en cuenta que esto no funciona con argumentos que no son serializables (como funciones, Symbols o objetos con referencias circulares). Para estos casos, considera usar una biblioteca especializada o implementar tu propia función de hashing.

Trap construct: Interceptando el Operador new

El trap construct se ejecuta cada vez que usas el operador new para crear una instancia de una función constructora o clase. Recibe tres parámetros: el target (la función constructora original), argumentsList (un array-like con los argumentos del constructor) y newTarget (el constructor que fue llamado originalmente). El trap debe devolver un objeto.

El trap construct es ideal para implementar patrones de diseño como Singleton, Factory, o para validar los argumentos pasados a un constructor antes de crear la instancia. También puedes usarlo para implementar logging de todas las instanciaciones o para modificar el comportamiento del constructor.

Uso Básico del Trap construct

Este ejemplo muestra cómo usar el trap construct para interceptar todas las instanciaciones de una clase y agregar logging. El trap recibe el constructor, los argumentos y el newTarget, y usa Reflect.construct para crear la instancia.

construct-basico.js
Loading code...

El trap construct intercepta todas las instanciaciones usando el operador new. En este ejemplo, registramos qué clase se está instanciando, los argumentos que se pasan, y la instancia creada. Luego usamos Reflect.construct para crear la instancia original.

Patrón Singleton con construct

El patrón Singleton asegura que una clase tenga una única instancia. Puedes implementar este patrón fácilmente usando el trap construct para verificar si ya existe una instancia y devolverla en lugar de crear una nueva.

singleton-con-construct.js
Loading code...

Este ejemplo muestra cómo implementar el patrón Singleton usando el trap construct. La primera vez que llamas a new Database(), se crea una nueva instancia. Las llamadas posteriores devuelven la misma instancia, asegurando que solo exista una conexión a la base de datos.

Alternativa: Variable de Instancia

Para implementar Singleton de forma más simple, puedes usar una variable estática en la clase (Database.instance) para almacenar la instancia única. Sin embargo, el trap construct ofrece más flexibilidad porque puedes implementar lógica condicional (por ejemplo, permitir múltiples instancias en modo de testing) y aplicar el mismo patrón a múltiples clases sin modificarlas.

Memoización de Funciones

La memoización es una técnica de optimización que cachea los resultados de funciones costosas para evitar recalcularlos. El trap apply es perfecto para implementar memoización porque intercepta todas las llamadas a la función y puede verificar si los argumentos ya fueron procesados anteriormente.

memoizacion-avanzada.js
Loading code...

Este ejemplo muestra una implementación más avanzada de memoización que soporta múltiples argumentos y permite limpiar el cache. La función memoizada cachea los resultados basándose en los argumentos y devuelve el resultado cacheado si los argumentos son los mismos.

Validación de Argumentos

Los traps apply y construct son ideales para implementar validación de argumentos. Puedes verificar que los argumentos cumplan ciertas condiciones antes de llamar a la función o crear la instancia. Si la validación falla, puedes lanzar un error específico que describe el problema.

validacion-argumentos.js
Loading code...

Este ejemplo muestra cómo implementar validación de argumentos para funciones y constructores. La función divide valida que el divisor no sea cero, y el constructor Usuario valida que el nombre sea una cadena no vacía y la edad sea un número positivo.

Validación vs TypeScript

TypeScript proporciona validación en tiempo de compilación, pero no en tiempo de ejecución. Los traps apply y construct te permiten agregar validación en tiempo de ejecución, lo que es útil cuando trabajas con datos de fuentes externas (APIs, formularios, archivos) que no están tipificados en TypeScript.

Errores Comunes

Al trabajar con los traps apply y construct, 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.apply

Un error común es intentar llamar a la función directamente dentro del trap apply en lugar de usar Reflect.apply. Esto puede causar problemas con el valor de this y no garantizar el comportamiento correcto.

error-olvidar-reflect-apply.js
Loading code...

Este ejemplo muestra el problema de no usar Reflect.apply. Cuando llamas a la función directamente con target(...args), el valor de this puede no ser el esperado, especialmente si la función depende de this. Siempre usa Reflect.apply para garantizar el comportamiento correcto.

Por Qué Usar Reflect.apply

Reflect.apply(target, thisArg, argumentsList) garantiza que la función se llame con el valor de this correcto y maneja correctamente casos especiales como funciones arrow, funciones bind y funciones que dependen del contexto. Usar target(...args) directamente puede causar bugs sutiles.

Error: No Devolver un Objeto en construct

El trap construct debe devolver un objeto. Si devuelves undefined, null o un valor primitivo, JavaScript lanzará un TypeError. Siempre asegúrate de devolver el resultado de Reflect.construct o un objeto válido.

error-no-devolver-objeto-construct.js
Loading code...

Este ejemplo muestra el problema de no devolver un objeto en el trap construct. Si el trap construct no devuelve un objeto, JavaScript lanza un TypeError porque el operador new siempre espera recibir un objeto.

Error: Bucle Infinito en apply

Un error común es crear un bucle infinito dentro del trap apply. Esto ocurre cuando el trap intenta llamar a la función directamente en lugar de usar Reflect.apply, lo que hace que el trap se ejecute recursivamente.

error-bucle-infinito-apply.js
Loading code...

Este ejemplo muestra cómo crear accidentalmente un bucle infinito. Dentro del trap apply, llamamos a la función directamente con target(...args). Como target es el Proxy, esto hace que el trap apply se ejecute nuevamente, creando un bucle infinito que causa un "Maximum call stack size exceeded".

Resumen: Traps apply y construct

Conceptos principales:

  • El trap apply intercepta llamadas a funciones (funcion())
  • El trap construct intercepta el operador new (new Constructor())
  • apply recibe target, thisArg y argumentsList como parámetros
  • construct recibe target, argumentsList y newTarget como parámetros
  • Ambos traps permiten implementar memoización, logging y validación

Mejores prácticas:

  • Usa siempre Reflect.apply y Reflect.construct dentro de los traps
  • Implementa memoización para funciones costosas para mejorar rendimiento
  • Usa construct para implementar patrones como Singleton y Factory
  • Valida argumentos en apply y construct para prevenir errores
  • Asegúrate de devolver un objeto en el trap construct