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.
