Entendiendo clases y herencia en Javascript
Antes que nada, quiero enfatizar que las clases en javascript como tal no existían antes de ES5, las clases en javascript son una mejora sintáctica basada en prototypes de javascript para simplificar y solventar el inconveniente de herencia cuando se utilizaban funciones para simular clases.
La sintaxis de las clases son similares a otros lenguajes de programación donde se expresan con la palabra reservada class
Veamos un ejemplo básico de una clase de javascript
Seguramente si vienes de otro lenguaje de programación, esto te resulte familiar, porque efectivamente tiene similitudes a otros lenguajes.
Pero vayamos al punto; las clases inician con la palabra reservada class seguido de un nombre genérico (el que queramos)
Si bien no es regla obligatoria, es buena práctica que el nombre de la clase sea uper camelcase
Las clases pueden estar estructuradas de la siguiente manera.
- Propiedades
- Constructor
- Métodos
- Setters
- Getters
- Propiedades privadas
- Métodos estáticos
- Getters estáticos
- Setters estáticos.
Veamos un punto importante antes de profundizar en cada uno de los items anteriores.
¿Cómo creo una instancia de clase?
Una instancia de clase se crea con la palabra reservada new tal como vemos en la imágen anterior, desde el momento que hacemos un
const persona = new Persona()
De si vamos a pasarle parámetros a la instancia, es dada por el constructor, aunque si el constructor recibe parámetros y no se los mandamos, javascript no nos dará error, y las propiedades que estamos asignando en el constructor, quedarán como undefined.
¿Por qué no muestras un error javascript? 😞
Propiedades
Las palabras nombre, apellido, son conocidos como propiedades de clase, y éstas pueden ser modificadas desde el constructor al momento de crear una instancia de clase, o pueden ser modificadas desde los setters.
Oooo… al menos eso quisiera; pero javascript es muy flexible, y las propiedades pueden ser modificadas directamente desde la instancia de clase, (lo cual te pido enormemente que no lo hagas así.)
Si hacemos lo siguiente
class Persona {
nombre = 'Kevin Méndez'
}const nuevaPersona = new Persona()
console.log(nuevaPersona.nombre) // Kevin MéndeznuevaPersona.nombre = 'Ezequiel Orellana'
console.log(nuevaPersona.nombre) // Ezequiel Orellana
Javascript ha modificado la propiedad nombre sin la necesidad de hacerlo mediante un setter lo cual lo considero hasta un antipatrón, así que procura no hacerlo de esta manera, las propiedades solo deben de ser modificadas desde dentro de la clase, o con los setters.
No te preocupes si no sabes qué son los setters más adelante las estudiaremos un poco.
Constructor
Después de las propiedades va un constructor que puede o no recibir parámetros, estos parámetros serán recibidos cuando se cree una nueva instancia de clase.
class Persona {
nombre;
apellido;
constructor(nombre, apellido = 'Méndez'){
this.nombre = nombre;
this.apellido = apellido
}
}
Los parámetros pueden tener valores por defecto, los cuales servirán cuando creamos una instancia como la siguiente:
...
const instancia1 = new Persona('Kevin')
console.log(instancia1.nombre) // Kevin
console.log(instancia2.apellido) // Méndezconst instancia2 = new Persona ('José', 'Marín')
console.log(instancia2.nombre) // Kevin
console.log(instancia2.apellido) // Marín
En la instancia1 solo envíamos un parámetro, el cual es “requerido”, pero fácilmente podemos evitar enviar el segundo parámetro porque ya tiene un valor por default.
En la instancia2 enviamos tanto nombre, como apellido, aunque apellido tenga un valor por default, pero si lo envíamos en el constructor, éste reemplazará el valor por defecto que tenemos.
Algo importante a tener en cuenta es que:
- El constructor es el primer “método” que se ejecutará al momento de crear una nueva instancia de la clase.
- El constructor siempre te devolverá una nueva instancia de clase, es decir, por cada vez que hagas un new Persona javascript creará un nuevo espacio de memoria en tu máquina física.
- Por default, las clases hacen uso interno de use strict
Dentro de las clases javascript solo podemos tener un constructor, porque lastimosamente no podemos tener múltiples constructores como en otros lenguajes. (java es un ejemplo claro) si bien existen algunos tips para poder simular los múltiples constructores, personalmente no me gusta, y no lo aplico a ninguno de mis proyectos.
Métodos
Los métodos son funciones que van dentro de la clase, el cual sirven para ejecutar fragmentos de código y poderlos reutilizar ya sea dentro de la clase, o fuera de; ejecutándose mediante la instancia de clase.
Además, los métodos son muy útiles al momento de hacer herencia entre clases, y poder reutilizarlos desde diferentes clases.
Los métodos tradicionalmente van abajo del constructor.
class Persona {
nombre;
apellido;
constructor(nombre, apellido = 'Méndez'){
this.nombre = nombre;
this.apellido = apellido
} nombreCompleto(){
return ´${this.nombre} ${this.apellido}´
} mostrarNombre(){
console.log(this.nombre)
}
}const instancia = new Persona('Kevin')
const nombreC = instancia.nombreCompleto()console.log(nombreC) // Kevin Méndezconsole.log(instancia.mostrarNombre()) // Kevin
Los métodos pueden retornar valores, cualquier valor que quieras; desde strings, numbers, arrays, objects, Symbol, lo que quieras.
La función nombreCompleto es de esas funciones que devuelven un valor (en este caso, nos devuelve el nombre completo)
Pero mostrarNombre no retorna nada, a simple vista, ya que los métodos que no retornan implícitamente un valor, javascript retornará un undefined.
Setters
Los setters solo tienen una función: establecer nuevos valores a las propiedades de la clase.
Los setters se definen similar a los métodos, a diferencia que se definen con la palabra reservada set el cual le dice a javascript que el método nombrePersona será el encargado de asignarle un nuevo valor a la propiedad nombre.
Los setters siempre deben de recibir un valor, pero es estrictamente necesario que éstos sólo reciban un solo parámetro, no más. Si es posible mandar más de un parámetro, pero esa no es su finalidad. Debemos respetarla.
class Persona {
nombre;
apellido;
constructor(nombre, apellido = 'Méndez'){
this.nombre = nombre;
this.apellido = apellido
} set nombrePersona(valor) { this.nombre = valor }}const instancia = new Persona('Kevin')
instancia.nombrePersona = 'Nuevo nombre'console.log(instancia.nombre) // Nuevo nombre
Getters
Los getter son prácticamente lo contrario a los setters, los getters sirven para obtener los valores de las propiedades, o la mutación de algún objeto, array lo lo que sea que tengas en la clase.
Los getters inician con la palabra reservada get seguido de una función que retorna un valor. Para los getters es obligatorio que retorne un valor, pueden retornar cualquier tipo primitivo de javascript.
class Persona {
nombre;
apellido;
constructor(nombre, apellido = 'Méndez'){
this.nombre = nombre;
this.apellido = apellido
} get getNombre() { return this.nombre }}const instancia = new Persona('Kevin')console.log(instancia.getNombre) // 'Kevin'
Es de tener en cuenta que los getters, javascript los toma como propiedades de clase; por lo que no es necesario que la función getNombre sea llamada con los paréntesis ()
Ahora si me preguntan cuál es la mejor manera de nombrar las funciones; en lo personal siempre antepongo primero la palabra get, esto para identificar una getter de una propiedad.
Aunque esto no es del todo estricto, he visto que hay quienes nombran los getters anteponiendo un _ seguido del nombre de la propiedad/propiedades.
Propiedades privadas
Las propiedades privadas existen desde hace mucho en otros lenguajes de programación, y en javascript es desde hace poco que se ha implementado (ES6 para ser exacto) por lo que no todos los navegadores lo soportan de forma nativa; es decir, sin necesidad de precompiladores con ts o webpack. Es de tenerlo en cuenta, sobretodo por aquellos usuarios que nunca actualizan los navegadores.
Antes que nada es de aclarar que las propiedades privadas, se les antepone el signo # para definirla como privada.
Pero ajá, ¿para qué sirven las propiedades privadas?
Imaginemos el siguiente escenario: tenemos una clase llamada Persona, con las propiedades nombre, y edad, pero recuerden que a una persona, en la vida real no es como que de la nada les vayan a cambiar el nombre ¿o sí? en cambio con la edad, sí, se nos “actualiza” cada año jajaja
Entonces en nuestra clase, podemos definir la propiedad nombre como privada. Ojo, las propiedades privadas pueden ser modificadas, pero solo desde dentro de una clase, es decir, no nos permite modificar dicha propiedad desde la instancia de la clase.
class Persona {
#nombre;
edad;
constructor(nm, ed) {
this.#nombre = nm
this.edad = ed
}
} const persona = new Persona("Kevin", 25) persona.#nombre = "Nuevo nombre"
Al hacer el persona.#nombre nos mostrará el siguiente error
Uncaught SyntaxError: Private field ‘#nombre’ must be declared in an enclosing class
Recuerden que debe de ser con # porque sino, js nos creará una nueva propiedad.
Métodos, getters, y setter estáticos
Las propiedades estáticas siempre inician con la palabra reservada static y una de las características es que pueden ser asignada, sin la necesidad de la palabra reservada new
Una propiedad puede ser estática, pero no es solamente una características de las propiedades, así mismo los get, y funciones puede ser de tipo static; pero tengamos en cuenta que los métodos y get static sólo pueden acceder a propiedades static.
¿Qué significa esto?
Que si desde un método static tratamos de acceder a una propiedad que no es static, ésta no obtiene ningún valor.
viéndolo de otra forma, es como que si las propiedades, getters y métodos estáticos estuvieran bajo otro scope de la clase.
class Persona {
static nombre = 'Kevin Méndez';
constructor() {}
}
const nombre = Persona.nombre
console.log(nombre) // Kevin Méndez
Como ven las propiedades static pueden ser llamadas directamente; sin necesidad de una instancia de clase.
De la misma manera funcionan los set, get y métodos static.
¡Pruébalos tú mismo!
Herencia
La herencia es una herramienta muy poderosa cuando hacemos POO en programación, no importa el lenguaje; la idea es la misma.
Veamos cómo se implementa en js.
En js la herencia de identifica con la palabra reservada extends, pero; ¿para qué usamos herencia?
La herencia sirve para extender nuestra clase a otra clase, es decir, si tenemos dos clases, y queremos “hacer que ambas clases sean una sola”; podemos hacerlo mediante la herencia. Veámoslo en un ejemplo
class Padre {
nombre;
apellido;
constructor(nm, ap) {
this.nombre = nm
this.apellido = ap
}
nombreCompleto(){
return this.nombre + ' ' + this.apellido
}
} class Hija extends Padre {
edad = 20;
constructor(nombre, apellido){
super(nombre, apellido)
}
}
¿Qué está pasando acá? ¿Por qué a la clase Hija le tengo que pasar el nombre y apellido?
¿Qué es eso de super()?
Esto es debido a que la clase Padre tiene un constructor que recibe dos argumentos, y debido a que está siendo heredada, mediante constructor se deben de pasar a la clase hija, pero la clase Hija, aparte de tener las propiedades de nombre y apellido (porque se heredan de la clase padre) también tiene la propiedad edad, pero esta propiedad pertenece solo a la clase Hija, por lo que si intentamos hacer una instancia en bruto de Padre, y acceder a la propiedad de edad, nos devolvería un undefined, pero no si lo hacemos desde la clase hija.
La palabra reservada super, hace referencia al constructor de la clase padre
Las propiedades no son las únicas que se pueden llamar desde la clase Hija, también pueden ser los setters, getters, e incluso métodos.
...const hija = new Hija("Kevin", "código") console.log(hija.nombreCompleto()) // Kevin Méndez
Hasta acá el post referente a clases y herencia en javascript
PD: si necesitas que nuestras clases sean aún más poderosas; podemos utilizar typescript para nuestro código, y poder utilizar esNEXT para tener lo último de lo último en js, y lo mejor de todo; sin perder compatibilidad con viejos navegadores.
Te invito a que tu siguiente paso, sea estudiar clases en typescript.
Por cierto, puedes invitarme a un café si mi publicación ha sido de tu agrado.
Cualquier duda, puedes contactarme mediante mi twitter