Clases en JavaScript ES6+: herencia, campos privados con # y métodos estáticos

Las clases de ES6 son la forma moderna de organizar código orientado a objetos en JavaScript. Aunque por debajo siguen usando el sistema de prototipos, la sintaxis es mucho más legible y expresiva. ES2022 añadió campos privados reales con el prefijo #, cerrando el último hueco que separaba las clases de JavaScript de las de otros lenguajes.

Estructura básica de una clase

Una clase define el constructor, los métodos de instancia, los métodos estáticos y los getters/setters en un bloque único y claro:

class Producto {
  constructor(nombre, precio) {
    this.nombre = nombre;
    this.precio = precio;
  }

  conIva(tipo = 0.21) {
    return this.precio * (1 + tipo);
  }

  toString() {
    return `${this.nombre}: ${this.precio}€`;
  }
}

const libro = new Producto('JavaScript avanzado', 29.99);
console.log(libro.conIva());      // 36.29
console.log(libro.toString());    // "JavaScript avanzado: 29.99€"
console.log(String(libro));       // "JavaScript avanzado: 29.99€"

Herencia con extends y super

extends establece la herencia entre clases. super() llama al constructor de la clase padre y es obligatorio en el constructor hijo antes de usar this:

class Animal {
  constructor(nombre) {
    this.nombre = nombre;
    this.vivo = true;
  }

  describir() {
    return `Soy ${this.nombre}`;
  }
}

class Perro extends Animal {
  constructor(nombre, raza) {
    super(nombre);  // OBLIGATORIO antes de usar this
    this.raza = raza;
  }

  ladrar() {
    return `${this.nombre} (${this.raza}) dice: ¡Guau!`;
  }

  describir() {
    return super.describir() + `, un perro ${this.raza}`;
  }
}

const rex = new Perro('Rex', 'Labrador');
console.log(rex.ladrar());    // "Rex (Labrador) dice: ¡Guau!"
console.log(rex.describir()); // "Soy Rex, un perro Labrador"
console.log(rex instanceof Animal);  // true
console.log(rex instanceof Perro);   // true

Campos privados con #

El prefijo # crea campos verdaderamente privados: no se pueden leer ni modificar desde fuera de la clase, ni siquiera con Object.keys() o mediante herencia:

class CuentaBancaria {
  #saldo = 0;  // Campo privado
  #historial = [];

  constructor(titular, saldoInicial) {
    this.titular = titular;
    this.#saldo = saldoInicial;
  }

  depositar(cantidad) {
    if (cantidad <= 0) throw new Error('Cantidad debe ser positiva');
    this.#saldo += cantidad;
    this.#historial.push(`+${cantidad}`);
  }

  retirar(cantidad) {
    if (cantidad > this.#saldo) throw new Error('Saldo insuficiente');
    this.#saldo -= cantidad;
    this.#historial.push(`-${cantidad}`);
  }

  get saldo() { return this.#saldo; }
  get historial() { return [...this.#historial]; }
}

const cuenta = new CuentaBancaria('Ana', 1000);
cuenta.depositar(500);
cuenta.retirar(200);
console.log(cuenta.saldo);      // 1300
console.log(cuenta.historial);  // ['+500', '-200']

// Acceso directo bloqueado:
console.log(cuenta.#saldo);  // SyntaxError

Métodos y campos estáticos

Los miembros estáticos pertenecen a la clase, no a las instancias. Son accesibles directamente en la clase y se usan para utilidades o fábricas:

class Temperatura {
  static #conversiones = {
    C_a_F: (c) => (c * 9/5) + 32,
    F_a_C: (f) => (f - 32) * 5/9,
    C_a_K: (c) => c + 273.15
  };

  #valor;
  #unidad;

  constructor(valor, unidad = 'C') {
    this.#valor = valor;
    this.#unidad = unidad;
  }

  static desdeCelsius(valor) {
    return new Temperatura(valor, 'C');
  }

  static desdeFahrenheit(valor) {
    return new Temperatura(valor, 'F');
  }

  aCelsius() {
    if (this.#unidad === 'C') return this.#valor;
    return Temperatura.#conversiones.F_a_C(this.#valor);
  }

  toString() {
    return `${this.#valor}°${this.#unidad}`;
  }
}

const fiebre = Temperatura.desdeCelsius(38.5);
const exterior = Temperatura.desdeFahrenheit(68);
console.log(fiebre.toString());       // "38.5°C"
console.log(exterior.aCelsius());     // 20

Error habitual: olvidar super en el constructor

Si una clase extiende otra y defines un constructor, debes llamar a super() antes de cualquier uso de this. Si no hay constructor explícito, JavaScript llama a super() automáticamente con todos los argumentos:

class Base {
  constructor(x) { this.x = x; }
}

class Hija extends Base {
  constructor(x, y) {
    // this.y = y;  // ReferenceError: Must call super before
    super(x);       // Primero super
    this.y = y;     // Luego this
  }
}

// Sin constructor: hereda el de Base automáticamente
class Nieta extends Hija {}
const obj = new Nieta(1, 2);
console.log(obj.x, obj.y);  // 1 2

Los campos privados con # no se heredan y no son accesibles en clases hijas, lo que garantiza encapsulación real. Para compartir acceso controlado, usa getters y setters públicos o métodos protegidos por convención.

COMPARTE ESTE ARTÍCULO

COMPARTIR EN FACEBOOK
COMPARTIR EN TWITTER
COMPARTIR EN LINKEDIN
COMPARTIR EN WHATSAPP