Clases y POO en JavaScript
En esta lección
Con objetos literales puedes crear una ficha suelta. Pero si tu programa maneja muchos objetos del mismo tipo (alumnos, productos, pedidos) que comparten datos y comportamiento, conviene definir un molde a partir del cual crearlos. Ese molde es una clase.
Crear una clase
class Alumno {
constructor(nombre, nota) {
this.nombre = nombre;
this.nota = nota;
}
estaAprobado() {
return this.nota >= 5;
}
presentarse() {
return `Soy ${this.nombre} y tengo un ${this.nota}`;
}
}
- Por convención, el nombre de la clase empieza por mayúscula.
- El
constructores un método especial que se ejecuta al crear cada objeto. Ahí se crean sus propiedades. thises “este objeto”: el alumno concreto que se está creando o usando.- Los métodos se escriben sin la palabra
functiony sin comas entre ellos (a diferencia de los objetos literales).
Crear objetos: new
const ana = new Alumno("Ana", 8);
const luis = new Alumno("Luis", 4);
ana.presentarse(); // "Soy Ana y tengo un 8"
luis.estaAprobado(); // false
ana.nota = 9; // cada objeto tiene sus propios datos
A cada objeto creado con una clase se le llama instancia. Si olvidas new, obtienes un error: Class constructor Alumno cannot be invoked without 'new'.
Propiedades privadas
Con # delante del nombre, una propiedad solo se puede usar dentro de la clase. Es la forma de proteger datos que no deben tocarse desde fuera:
class CuentaBancaria {
#saldo = 0;
ingresar(cantidad) {
if (cantidad <= 0) throw new RangeError("La cantidad debe ser positiva");
this.#saldo += cantidad;
}
get saldo() {
return this.#saldo;
}
}
const cuenta = new CuentaBancaria();
cuenta.ingresar(100);
cuenta.saldo; // 100
cuenta.#saldo = 1e6; // SyntaxError: no se puede acceder desde fuera
Getters y setters
Un getter (get) es un método que se usa como si fuera una propiedad, sin paréntesis. Un setter (set) se ejecuta al asignar un valor, y permite validarlo:
class Temperatura {
#celsius = 0;
get fahrenheit() {
return this.#celsius * 9 / 5 + 32;
}
set celsius(valor) {
if (valor < -273.15) throw new RangeError("Por debajo del cero absoluto");
this.#celsius = valor;
}
}
const t = new Temperatura();
t.celsius = 25;
t.fahrenheit; // 77
Métodos estáticos
Un método static pertenece a la clase, no a cada objeto. Se llama con el nombre de la clase y no puede usarse desde una instancia.
¿Cuándo tiene sentido?
- Cuando la operación no necesita ningún objeto concreto:
Alumno.crearDesdeTexto("Ana;8"). - Cuando trabaja con varios objetos sin que ninguno sea más importante que otro.
class Punto {
constructor(x, y) {
this.x = x;
this.y = y;
}
static distancia(a, b) {
return Math.hypot(b.x - a.x, b.y - a.y);
}
}
Punto.distancia(new Punto(0, 0), new Punto(3, 4)); // 5
Ya conoces muchos métodos estáticos: Math.max(), Array.isArray(), Number.isNaN(), Date.now().
Herencia: extends y super
Una clase puede extender otra: hereda todas sus propiedades y métodos, y puede añadir los suyos o cambiar los que hereda.
class Empleado {
constructor(nombre, sueldo) {
this.nombre = nombre;
this.sueldo = sueldo;
}
describir() {
return `${this.nombre} (${this.sueldo} €)`;
}
}
class Jefe extends Empleado {
constructor(nombre, sueldo, equipo) {
super(nombre, sueldo); // llama al constructor de Empleado
this.equipo = equipo;
}
describir() {
return `${super.describir()}, dirige a ${this.equipo.length} personas`;
}
}
const marta = new Jefe("Marta", 3000, ["Ana", "Luis"]);
marta.describir(); // "Marta (3000 €), dirige a 2 personas"
marta instanceof Empleado; // true
- En el constructor de la clase hija hay que llamar a
super(...)antes de usarthis. super.metodo()llama a la versión de la clase padre.
¿Herencia sí o no?
La herencia es tentadora, pero tiene problemas conocidos cuando se abusa de ella:
- Acoplamiento: la clase hija depende de todos los detalles de la madre (y de la abuela…). Un cambio arriba puede romper todo lo de abajo.
- Rigidez: por bien que diseñes la jerarquía, aparecen casos que no encajan. ¿Un pingüino es un
AvesiAvetiene un métodovolar()? - El problema del gorila y el plátano: querías solo un plátano y te llevas el gorila que lo sostiene y la selva entera.
Por eso existe un principio muy conocido: prefiere la composición a la herencia. En lugar de “un Coche es un Motor con ruedas”, di “un Coche tiene un Motor”:
class Coche {
constructor(motor) {
this.motor = motor; // composición: el coche contiene un motor
}
arrancar() {
return this.motor.encender();
}
}
Usa la herencia para jerarquías cortas y claras (uno o dos niveles), y la composición para todo lo demás.
Debajo de las clases: los prototipos
JavaScript no nació con clases. Su modelo original de objetos se basa en prototipos: no hay moldes, solo objetos, y cada objeto puede “delegar” en otro objeto (su prototipo) las propiedades que no tiene. Es un modelo más flexible, como las piezas de Lego frente a los muebles con instrucciones cerradas.
Las clases llegaron en 2015 y son, en el fondo, una forma más cómoda de escribir prototipos (azúcar sintáctico). Se añadieron porque mucha gente venía de lenguajes como Java y las pedía, y porque para empezar resultan más fáciles de entender. No necesitas dominar los prototipos para usar clases, pero te ayudará saber que existen cuando leas código antiguo o mensajes de error que hablan de prototype.
Pruébalo tú
Cambia el código y pulsa Ejecutar (o Ctrl + Enter).
Pon a prueba lo que has aprendido
¿Te ha quedado claro? Márcala y verás tu progreso en el explorador.