JavaScript — Class ES6 (Parte V)
Temario
- Introducción
- Clases (clase, instanciar clase)
- Constructor
- Métodos públicos
- Métodos getters y setters (get, set, referencias circulares)
- Métodos estáticos
- Métodos privados (funciones globales, IIFE, nomenclatura(_), dentro del constructor, weakMap, nomenclatura(#))
- Propiedades públicas
- Propiedades estáticas
- Propiedades privadas (IIFE, nomenclatura(_), weakMap, nomenclatura(#))
- Herencia (Extend y super, mixins)
i. Introducción
Vamos por la quinta parte de ES6 (parte I[ref], parte II[ref], parte III[ref], parte IV[ref]).
Anteriormente vimos la teoría básica de Programación Orientada a Objetos[ref], así como los conceptos de clase, polimorfismo, etc…[ref] aunque los ejemplos son basados en prototipos, la teoría es la misma, por lo tanto los siguientes temas van a ser más prácticos que teóricos.
ii. Clases
a — Clase
La estructura más básica de una clase es la siguiente:
Recordemos que:
- El contenido de la clase se ejecuta en modo estricto de forma automática.
- La declaración de una clase no sigue la regla de hoisting[ref], esto quiere decir que solo van a existir hasta ser declaradas.
- Internamente se comporta como una constante const, por lo que no puede ser re-declarada.
- Una clase podemos encontrar propiedades (properties), su constructor (constructor), getters (get) y setters (set), así como métodos estáticos (static) y públicos (public)
b — Instanciar clase
Para instanciar una clase hacemos uso del operador new :
También podemos enviarle parámetros, pero antes de eso, veamos el método constructor.
iii. Constructor
No olvidemos que el constructor es el método encargado de crear nuestro objeto, e inicializar sus propiedades, cuando instanciamos una clase, automáticamente se ejecuta el constructor.
Y así es como podemos mandarle parámetros a una clase:
Al constructor le podemos pasar todos los parámetros necesarios para inicializar la clase, se recomienda pasar los parámetros como un objeto y en el constructor utilizar desestructuración.
iv. Métodos públicos
Podemos generar los métodos que sean necesarios para nuestra clase, y van a ser los métodos disponibles para invocarlos en nuestra aplicación.
v. Métodos getters y setters
La mayoría de los desarrolladores en JavaScript está acostumbrado a crear los métodos de get y set de la siguiente manera:
Pero pocos saben que en JavaScript ES6 tiene sus propios métodos de acceso get y set, veamos cómo funcionan:
a — get
El método de acceso get, es útil para obtener el valor de alguna propiedad, adaptemos nuestro ejemplo anterior:
La ventaja de usar el método de acceso get es que nos da la libertad de generar los getters necesarios (no necesariamente deben ser propiedades), siempre y cuando sirvan para retornar valores, como en nuestro ejemplo hemos creado get presentation().
Continuemos con nuestro ejemplo, hagamos una instancia y hagamos uso de los métodos de acceso:
Es importante observar en nuestro ejemplo que los métodos de acceso NO se ejecutan como si fueran un método sino que se mandar a llamar como una propiedad de nuestro objeto instanciado, un error muy común como desarrollador es ejecutarlo como si fuera una función person.presentation() o person.name() (estos retornan que el método no existe), así que se debe tener mucho cuidado.
Veamos el ejemplo completo en tiempo real:
b — set
El método de acceso set, es útil para asignar el valor de alguna propiedad, adaptemos nuestro ejemplo anterior:
Se ha agregado el método de acceso set para name y age.
Importante: En nuestro ejemplo podemos observar que las propiedades que se han creado llevan _, esto es MUY importante ya que vamos a evitar las referencias circulares (el siguiente tema se explica).
Continuemos con nuestro ejemplo, hagamos una instancia y hagamos uso de los métodos de acceso:
Veamos el ejemplo completo y en tiempo real:
c — Referencias circulares
La referencia circular comúnmente se da cuando manejamos objetos (exacto en JavaScript todo son objetos) y se da cuando se hace referencia a sí mismo (recursión) y con ello llega el desbordamiento de pila, fuga de memoria, errores, caos…
Veamos un ejemplo muy simple:
Creamos un objeto tipo Array, donde en su posición 0 le asignamos el mismo arreglo que hemos creado.
Para entenderlo mejor, vamos a hacerlo por pasos:
- Cuando nosotros creamos una variable, se asigna en memoria en algún lugar:
Cuando nosotros asignamos al mismo objeto arr[0] = arr, lo que hacemos es lo siguiente:
Con este ejemplo sencillo hemos entendido la referencia circular; veamos otro ejemplo:
Pasa exactamente lo mismo que en nuestro ejemplo de arriba:
Cuando el setter y la propiedad tienen el mismo nombre se crea una llamada a sí misma por lo que hace una función recursiva infinita, hay que tener mucho cuidado cuando usemos setters y getters.
vi. Métodos estáticos
Los métodos estáticos son ejecutados desde la propia clase y no desde su instancia, se usan mayormente para crear funciones para una aplicación (dicho de otra manera es lo que conocemos como helpers).
Al ser llamados sin crear una instancia estos NO tienen acceso a los datos de un objeto.
Veamos un ejemplo, de su uso correcto:
vii. Métodos privados
Con JavaScript tenemos diferentes maneras de agregar métodos privados:
a — Funciones globales
Haciendo usos de funciones globales, y utilizando el método bind.
Veamos el ejemplo en tiempo real:
b — IIFE
Podemos envolver nuestros métodos y clase dentro de una IIFE[ref], por lo que las funciones se vuelven locales.
Spoiler: Este ejemplo es un patrón en JavaScript (que ya veremos más adelante en otra story).
Veamos el ejemplo en tiempo real:
c — Nomenclatura (_)
Es un prefijo que se utiliza en muchos lenguajes de programación para indicar un método o propiedad como privada, aunque realmente para las clases en JavaScript no aplica, ya que siguen siendo públicas.
Esta forma de declararlo es de las más recomendadas, ya que la mayoría de los desarrolladores al ver una variable _ entenderá que no se debe tocar.
Veamos el ejemplo en tiempo real:
d — Dentro del constructor
Se necesitan agregar todos los métodos a la instancia dentro del constructor (al menos aquellos métodos que necesitan acceso a los datos privados).
Esta forma no la recomiendo mucho ya que debido a los métodos de instancia, el código desperdicia bastante memoria.
Veamos el ejemplo en tiempo real:
e — WeakMap
Otra alternativa es usar WeakMap[ref], es una colección de pares clave/valor en la que las claves son objetos y los valores son valores arbitrarios.
Veamos el ejemplo en tiempo real:
f — Nomenclatura (#)
Hay una propuesta para el nuevo estándar de JavaScript, que soporta métodos y propiedades privadas. Para eso deben de comenzar con #, y con esto las propiedades y métodos se vuelven privadas (solo es posible acceder a ellos dentro de la clase).
Importante: Al no ser un estándar todavía es posible que no funcione en todos los navegadores, inclusive marque errores, así que por ahora no se recomienda usar de esta forma.[ref][ref]
viii. Propiedades públicas
Las propiedades definen el estado de nuestro objeto, en las clases tenemos dos formas de inicializarlas:
- Cuando se ejecuta el constructor
- Por medio de un método set (previamente declarado)
Veamos el ejemplo en tiempo real:
ix. Propiedades estáticas
Las propiedades estáticas son accesibles desde la propia clase y no desde su instancia, se usan mayormente para crear propiedades para una aplicación (dicho de otra manera es lo que conocemos como constantes o helpers).
Importante: Las propiedades estáticas funcionan solo en los navegadores más nuevos, y se tiene problema al implementarlas con la herencia, por lo que se recomienda su uso con mucho cuidado.
Veamos el ejemplo en tiempo real:
x. Propiedades privadas
Como hemos venido explicando hasta ahora no hay una solución estándar y elegante para los métodos privados, lo mismo pasa para las propiedades privadas; así que veremos diferentes ejemplos de cómo poder lograrlo.
a — IIFE
Podemos envolver nuestras propiedades y clase dentro de una IIFE, por lo que las variables se vuelven locales.
En el ejemplo anterior hemos usado el método de acceso get para obtener name.
Veamos el ejemplo en tiempo real:
b — Nomenclatura (_)
Es un prefijo que se utiliza en muchos lenguajes de programación para indicar un método o propiedad como privada, aunque realmente para las clases en JavaScript no aplica, ya que siguen siendo públicas.
Solo nos queda confiar que se obtenga el valor con el método de acceso get, ya que si hacemos esto : foo._name = ‘Benjamín’; realmente si cambia su valor.
Veamos el ejemplo en tiempo real:
c — WeakMap
Otra alternativa es usar WeakMap, es una colección de pares clave/valor en la que las claves son objetos y los valores son valores arbitrarios.
Veamos el ejemplo en tiempo real:
d — Nomenclatura (#)
Antes JavaScript no contaba con modificadores de acceso para indicar que la propiedad es privada, con las últimas actualizaciones para indicar que una propiedad es privada se usa el operador #
Importante: Al no ser un estándar todavía es posible que no funcione en todos los navegadores, inclusive marque errores, así que por ahora no se recomienda usar de esta forma.[ref][ref]
xi. Herencia
Ya hemos explicado cómo usar clases, entendemos que tiene métodos y propiedades, ahora vamos a ver el concepto de herencia, donde haremos uso del extend y super, que son palabras clave para las clases.
Una de las ventajas de usar la herencia entre clases es de que no necesitamos duplicar características que tenemos en común con otras clases, veamos primero el siguiente ejemplo:
Podemos observar que tenemos una clase base llamada Animal y tiene subclases más específicas Horse, Dog, Lion, Whale.
a — Extend y super
Veamos cómo se vería en código la imagen anterior:
Para hacer breve el ejemplo, vamos a ejemplificar Horse:
La clase Horse es una subclase de Animal donde se usa la palabra reservada extend para poder heredar todas las propiedades y métodos de la clase base.
Ahora la palabra reservada super, se ocupa de dos maneras:
- En el constructor se usa como una función: Lo que hace es llamar al constructor de la clase base Animal.
- Ahora dentro del método dailyRoutine se ha usado como un objeto ya que se usa para llamar a los métodos de la clase base, esto es porque hace referencia a la instancia de la clase base Animal
Pero entonces, cuando se necesite usar un método de la clase padre Animal, ¿debo usar la palabra super como objeto?, ¿dónde queda la herencia?… realmente podemos usar this (la idea de este ejemplo es identificar super como función y como objeto), por lo que puede quedar de la siguiente forma:
Se va a comportar exactamente de la misma manera (gracias a la herencia).
Veamos el ejemplo completo en tiempo real:
b — Herencia múltiple (mixins)
Hasta aquí hemos visto la herencia simple. Pero, ahora imaginemos que queremos convertir nuestros caballos en animales de carrera, por lo que necesitamos saber:
- Cuántas carreras ha ganado
- Cuántas ha perdido
- Sus estadísticas generales
Estoy seguro que la solución que has pensado es “Agregar métodos y propiedades a nuestra clase Horse”; no está mal como una solución a corto plazo, pero respondamos la siguiente pregunta:
¿Realmente todos los caballos son usados para carreras?
La respuesta es NO, ya que podemos tener animales para granjas, para trabajo, etc, entonces a nuestra clase estaríamos agregando métodos y propiedades que no son propias de la clase Horse; entonces aquellos que usen nuestra clase va a estar obligados a sobreescribir métodos y propiedades que no necesiten (Spoiler: Romperiamos con un principio SOLID[**]).
Importante: En ES6 solo se puede tener una clase padre, por lo que la herencia múltiple de manera directa no es posible, para eso se usan los mixins.
Para esto usamos los mixins, recordemos algunos conceptos que ya hemos visto…
¿Recuerdan cómo es una expresión de función[ref]?
Así como existen expresiones de funciones, también existen expresiones de clase:
Estamos usando expresión de función que retorna una clase que a su vez extiende expresiones (suena confuso lo sé).
Para usar el mixin en nuestras clases es de la siguiente manera:
Regresando a nuestro ejemplo, necesitamos una clase (en este caso un mixin) que tendrá métodos y propiedades; por lo que quedaría de la siguiente manera:
Veamos el ejemplo completo en tiempo real:
Al tener nuestra interfaz RacerMixin, tenemos la posibilidad de usarlo en más lados, por ejemplo, si ahora necesitamos que los perros Dog de carreras o un atleta Athlete, sólo nos va a bastar con extender el mixin y con esto tendríamos herencia múltiple.
¿Qué pasaría si necesitamos hacer múltiples herencias?, bueno, quedaría de la siguiente manera:
Veamos un ejemplo genérico:
El ejemplo en tiempo real:
Entre más mixins necesitemos más complejo de vuelve:
Gracias a Justin Fagnani[ref] podemos hacer más elegante el uso de los mixins dentro de la clase, la idea es poder construir una clase más semántica o parecida a otros lenguajes, para ello vamos a generar una expresión de clase genérica:
Y para hacer utilizarlo es de la siguiente manera:
Más amigable ¿no?
En la siguiente entrega vamos a ver HTML/JavaScript — Document Object Model (DOM)
La entrega pasada vimos JavaScript — Map, Set, Proxy, Reflect & Export modules ES6 (Parte IV)
Bibliografía y links que te puede interesar…