Herencia funcional en JavaScript

June 07, 2016

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.

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

Banner posts

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: Co mponer 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:

Veamos esto en un ejemplo

const humano = function (name) {
  return {
    nombre: name,
    genero: '',
    ciudad: 'Bogota',
    decirGenero() {
      console.log(this.nombre + ' mi genero es ' + this.genero)
    },
    decirCiudad() {
      console.log(this.nombre + ' vivo en ' + this.ciudad)
    }

  };
}

const hombre = function (name) {
  const that = humano(name);

  return Object.assign({},
    that,
    { genero: 'Masculino' }
  )
}

const mujer = function (name) {
  const that = humano(name);

  return Object.assign({},
    that,
    { genero: 'Masculino' }
  )
}
const david = hombre('David');
const jane = mujer('Jane');

david.decirGenero();
david.decirCiudad();

jane.decirGenero();
jane.decirCiudad();

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.

const contador = function (salto) {
  let contador = 0;

  return {
    siguiente() {
      contador += salto;
    },
    ver() {
      console.log(contador)
    },
    reset() {
      contador = 0;
    }
  }
}

const contadorDos = contador(2);


contadorDos.siguiente()
contadorDos.ver() // 2
contadorDos.siguiente()
contadorDos.ver() // 4
contadorDos.reset()
contadorDos.siguiente()
contadorDos.ver() // 2

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

  • Express al crear una aplicación, retorna un objeto
  • Jquery, al usar $(Selector), retorna un objeto

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.

  1. 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.00002ms

Factory: 0.00004ms

Literal: 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.

  1. 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óximospost, 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 dela 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.