Herencia funcional en JavaScript
Si JavaScript no tiene clases, ¿como se implementa la herencia? es tal vez una de las preguntas que todo desarrollador se ha planteado, en este post intentaremos terminar de dar respuesta esto.
Nota: Este contenido lo publiqué primero en mi newsletter, la semana después de publicar el newsletter publico en mi blog, si quieres ser el primero en leer suscríbete.
En JavaScript: The Good Parts escrito por Douglas Crockford, habla sobre factory functions, las cuales son la base para realizar herencia funcional (no confundir con programación funcional)
Recuerda que existen tres maneras de realizar herencia en JavaScript:
- Delegación: Delegar comportamientos a objetos vinculados vía [[Prototype]]
- Composición: Componer objetos basados en ejemplos o prototipos, vía Object.assign()
- Funcional: Componer objetos mediante funciones.
Hoy hablaremos de la tercera, pero vayamos paso a paso.
Herencia funcional
Como ya dije, el promotor de esta es Douglas Crockford, quien en gran parte de sus conferencias nos habla de los peligros de la herencia clásica y el uso de this en JS.
Él promueve un sistema de objetos libre de clases, planteando el uso de factory functions, estas básicamente son funciones que utilizan varios conceptos, como:
- Poder retornar objetos en una función.
- Poder crear objetos literales.
- La naturaleza dinámica de los objetos.
- Closures.
Veamos esto en un ejemplo
Como vemos, simplemente tenemos funciones que retornan objetos, los cuales pueden ser genéricos (humano) o pueden ser más específicos (mujer y/o hombre), cada función nos va a retornar un objeto independiente.
Pero el poder de las factory functions no queda en esto, con ellas y utilizando el concepto de closures podemos tener propiedades privadas.
Como vemos, tenemos una propiedad (contador), la cual no puede ser accedida desde los objetos creados por esta factory, pero los métodos en este si pueden hacer gracias a closure.
Como vemos es bastante simple, por eso se utiliza en gran número de librerías dentro de JavaScript, por ejemplo en
Ventajas
- Simplicidad: Solo se requieren unos cuantos pasos para conseguir realizar herencia.
- Libre de this: Podemos dejar de preocuparnos de this o como en los ejemplos su uso es bastante sencillo.
- Encapsulación: Objetos pueden tener miembros privados
- Cada objeto es único: Cada vez que se llama la función crea un objeto, de esta manera podemos controlar la mutación.
Para mi la principal ventaja de la herencia funcional es la simplicidad, ya que esta es la que principalmente afecta todo nuestro proyecto, te recomiendo leer este paper sobre la complejidad a los proyectos.
Desventajas
Existen dos grandes “desventajas” que toda persona nombra cuando se trata de herencia funcional o factory functions.
- El rendimiento creando objetos es menor.
Esto es cierto, pero veamos que tanto, si realizamos esta prueba donde medimos cuánto demora en crearse un objeto con objetos literales, factory functions y constructores, obtenemos:
Constructor: 0.00002msFactory: 0.00004msLiteral: 0.00001ms
Como vemos la velocidad de creación de un objeto es mínima y la diferencia para que llegue a ser significativa seria al crear al menos de diez mil objetos (aun asi sera solo de 2ms)
Otra consideración es que al usar una función constructora, aunque tiene mejor rendimiento, se debe usar muchas veces bind para tener el comportamiento deseado de this, esto también afecta el rendimiento.
Nota: Estos valores pueden variar dependiendo del engine que uses y varios factores, aun así si decides realizar microbenchmarks como este, te recomiendo ver antes esta conferencia.
2. El consumo de memoria es mayor
Esto también es cierto ya que cada objeto creado con una factory function se creará y guardará en memoria como un objeto independiente.
Pero con el valor actual de memoria, ¿es justificable esta desventaja?
Herencia funcional vs herencia clásica
Una consideración importante a mi parecer es la simplicidad y la flexibilidad, tomando solo esos dos factores en cuenta considero que las factory functions son una mejor opción.
Pero si se desea priorizar el rendimiento (micro optimizando) la mejor opción sería usar funciones constructoras (clases), esta respuesta en quora te explicara mejor esto.
Conclusión
JavaScript nos provee de un sistema de herencia increíble, con el cual podemos crear sistemas grandes pero a su vez flexibles y mantenibles.
En este punto te preguntarás ¿eso es todo?, yo también me hice esa pregunta, la respuesta corta es sí y esto es lo increible de JavaScript, ser muy simple, pero a su vez poderoso.
Como vimos los tres tipos de herencia son bastante simples, pero se requiere practicar y saber como usarlos, en próximos post, traeré ejemplos sobre su implementación que nos permitan explorar estos más a fondo, por ahora te recomiendo leer este post es muy bueno y te mostrará la flexibilidad de la herencia en JS, también buscar repositorios de librerías y leer el código es una practica que te ayudará a ser mejor desarrollador.