Saltar al contenido
clases.js · devschool

Clases y POO en JavaScript

Lección 23 de 38 · 5 min de lectura · Actualizado el

En esta lección
  1. Crear una clase
  2. Crear objetos: new
  3. Propiedades privadas
  4. Getters y setters
  5. Métodos estáticos
  6. Herencia: extends y super
  7. ¿Herencia sí o no?
  8. Debajo de las clases: los prototipos

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 constructor es un método especial que se ejecuta al crear cada objeto. Ahí se crean sus propiedades.
  • this es “este objeto”: el alumno concreto que se está creando o usando.
  • Los métodos se escriben sin la palabra function y 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 usar this.
  • 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 Ave si Ave tiene un método volar()?
  • 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).

clases.js
Consola

Pon a prueba lo que has aprendido

[JavaScript] En una clase hija, ¿qué hay que hacer en el constructor antes de usar this?

[JavaScript] ¿Qué significa el # en #saldo dentro de una clase?

¿Te ha quedado claro? Márcala y verás tu progreso en el explorador.