Envolver funciones de JavaScript te permite añadir lógica común a funciones que no controlas, como funciones nativas y externas. Muchas bibliotecas de JavaScript, como los agentes de TrackJS, necesitan envolver funciones externas para hacer su trabajo. Añadir envolturas nos permite escuchar la Telemetría, los errores y los registros en su código, sin necesidad de llamar a nuestra API explícitamente.
Es posible que desee envolver una función para añadir instrumentación o lógica de depuración temporal. También puede cambiar el comportamiento de una biblioteca externa sin necesidad de modificar el código fuente.
Basic Function Wrapping
Debido a que JavaScript es maravillosamente dinámico, podemos envolver una función simplemente redefiniendo la función con algo nuevo. Por ejemplo, considere esta envoltura de myFunction
:
En este ejemplo trivial, envolvimos el original myFunction
y añadimos un mensaje de registro. Pero hay muchas cosas que no manejamos:
- ¿Cómo pasamos los argumentos de la función?
- ¿Cómo mantenemos el ámbito (el valor de
this
)? - ¿Cómo obtenemos el valor de retorno?
- ¿Qué pasa si ocurre un error?
Para manejar estas cosas, tenemos que ser un poco más inteligentes en nuestra envoltura.
Nota que no sólo estamos invocando la función en este ejemplo, sino que la call
invocamos con el valor de this
y los argumentos a
, b
y c
. El valor de this
será pasado desde donde sea que adjunte su función envuelta, Window
en este ejemplo.
También rodeamos toda la función en un bloque try/catch
para que podamos invocar una lógica personalizada en caso de error, volver a lanzarla o devolver un valor por defecto.
Envoltura avanzada de funciones
El ejemplo básico de envoltura funcionará el 90% de las veces, pero si estás construyendo bibliotecas compartidas, como los agentes de TrackJS, ¡eso no es suficiente! Para envolver nuestras funciones como un profesional, hay algunos casos extremos que debemos tratar:
- ¿Qué pasa con los argumentos no declarados o desconocidos?
- ¿Cómo hacemos coincidir la firma de la función?
- ¿Cómo reflejamos las propiedades de la función?
Hay 3 cambios sutiles pero importantes. Primero (#1), nombramos la función. Parece redundante, pero el código de usuario puede comprobar el valor de function.name
, por lo que es importante mantener el nombre al envolver.
El segundo cambio (#2) está en cómo llamamos a la función envuelta, utilizando apply
en lugar de call
. Esto nos permite pasar a través de un objeto arguments
, que es un objeto tipo array de todos los argumentos pasados a la función en tiempo de ejecución. Esto nos permite soportar funciones que pueden tener un número indefinido o variable de argumentos.
Con apply
, no necesitamos los argumentos a
, b
, y c
definidos en la firma de la función. Pero al seguir declarando los mismos argumentos que la función original, mantenemos la aridad de la función. Es decir, Function.length
devuelve el número de argumentos definidos en la firma, y esto reflejará la función original.
El cambio final (#3) copia cualquier propiedad especificada por el usuario de la función original en nuestra envoltura.
Limitaciones
Esta envoltura es completa, pero siempre hay limitaciones en JavaScript. En concreto, es difícil envolver correctamente una función con un prototipo no estándar, como un constructor de objetos. Este es un caso de uso mejor resuelto por la herencia.
En general, cambiar el prototipo de una función es posible, pero no es una buena idea. Hay serias implicaciones de rendimiento y efectos secundarios no deseados en la manipulación de los prototipos.
Respeta el entorno
La envoltura de funciones te da mucho poder para instrumentar y manipular el entorno de JavaScript. Usted tiene la responsabilidad de manejar ese poder sabiamente. Si está construyendo envolturas de funciones, asegúrese de respetar al usuario y el entorno en el que está operando. Puede haber otras envolturas en el lugar, otros oyentes adjuntos para los eventos, y las expectativas en las API de la función. Pisa con cuidado y no rompas el código externo.