Herencia y super: reutilizar clases existentes
La herencia permite crear clases especializadas basadas en otras, reutilizando propiedades y métodos sin duplicar código.
extends: heredar de otra clase
Con extends, una clase hija adquiere todas las propiedades y métodos de la clase padre:
class Animal {
constructor(nombre) {
this.nombre = nombre;
}
describir() {
return `Soy ${this.nombre}`;
}
}
// Perro hereda todo de Animal
class Perro extends Animal {
ladrar() {
return "¡Guau!";
}
}
const rex = new Perro("Rex");
console.log(rex.describir()); // "Soy Rex" ← método heredado
console.log(rex.ladrar()); // "¡Guau!" ← método propioclass Animal {
nombre: string;
constructor(nombre: string) {
this.nombre = nombre;
}
describir(): string {
return `Soy ${this.nombre}`;
}
}
// Perro hereda todo de Animal
class Perro extends Animal {
ladrar(): string {
return "¡Guau!";
}
}
const rex = new Perro("Rex");
console.log(rex.describir()); // "Soy Rex" ← método heredado
console.log(rex.ladrar()); // "¡Guau!" ← método propioSoy Rex
¡Guau!Puedes verificar la jerarquía con instanceof: rex instanceof Perro → true, y también rex instanceof Animal → true. La instancia es de la clase hija y de todas sus ancestras.
super(): llamar al constructor padre
Cuando la clase hija tiene su propio constructor, debe llamar a super() antes de usar this. Esto inicializa la parte heredada del objeto:
class Animal {
constructor(nombre, tipo) {
this.nombre = nombre;
this.tipo = tipo;
}
}
class Perro extends Animal {
constructor(nombre, raza) {
// ✅ super() llama al constructor de Animal
super(nombre, "mamífero");
// Ahora podemos usar 'this'
this.raza = raza;
}
presentarse() {
return `Soy ${this.nombre}, un ${this.raza} (${this.tipo})`;
}
}
const luna = new Perro("Luna", "Beagle");
console.log(luna.presentarse());
// "Soy Luna, un Beagle (mamífero)"class Animal {
nombre: string;
tipo: string;
constructor(nombre: string, tipo: string) {
this.nombre = nombre;
this.tipo = tipo;
}
}
class Perro extends Animal {
raza: string;
constructor(nombre: string, raza: string) {
// ✅ super() llama al constructor de Animal
super(nombre, "mamífero");
// Ahora podemos usar 'this'
this.raza = raza;
}
presentarse(): string {
return `Soy ${this.nombre}, un ${this.raza} (${this.tipo})`;
}
}
const luna = new Perro("Luna", "Beagle");
console.log(luna.presentarse());
// "Soy Luna, un Beagle (mamífero)"Soy Luna, un Beagle (mamífero)Si defines un constructor en la clase hija y no llamas a super() antes de acceder a this, obtendrás un ReferenceError en tiempo de ejecución. TypeScript lo detecta en compilación.
Override: redefinir comportamiento
Una clase hija puede redefinir (override) cualquier método del padre. La versión de la hija tiene prioridad cuando se llama sobre una instancia de la hija:
class Animal {
constructor(nombre) {
this.nombre = nombre;
}
hacerSonido() {
return "...";
}
toString() {
return `Animal: ${this.nombre}`;
}
}
class Perro extends Animal {
// Override: redefinimos hacerSonido
hacerSonido() {
return "¡Guau!";
}
}
class Gato extends Animal {
// Override: cada clase define su propio sonido
hacerSonido() {
return "¡Miau!";
}
}
const animales = [new Perro("Rex"), new Gato("Michi"), new Perro("Luna")];
for (const animal of animales) {
console.log(`${animal.nombre}: ${animal.hacerSonido()}`);
}
// Rex: ¡Guau!
// Michi: ¡Miau!
// Luna: ¡Guau!class Animal {
nombre: string;
constructor(nombre: string) {
this.nombre = nombre;
}
hacerSonido(): string {
return "...";
}
}
class Perro extends Animal {
override hacerSonido(): string {
return "¡Guau!";
}
}
class Gato extends Animal {
override hacerSonido(): string {
return "¡Miau!";
}
}
const animales: Animal[] = [new Perro("Rex"), new Gato("Michi"), new Perro("Luna")];
for (const animal of animales) {
console.log(`${animal.nombre}: ${animal.hacerSonido()}`);
}
// Rex: ¡Guau!
// Michi: ¡Miau!
// Luna: ¡Guau!Rex: ¡Guau!
Michi: ¡Miau!
Luna: ¡Guau!La palabra clave override (TypeScript 4.3+) hace explícito que estás redefiniendo un método del padre. Si el método no existe en la clase padre, TypeScript lanzará un error — lo que previene typos difíciles de detectar.
super.metodo(): llamar la implementación del padre
Dentro de un override, puedes llamar la versión del padre usando super.metodo(). Útil para extender, no reemplazar completamente:
class Empleado {
constructor(nombre, salarioBase) {
this.nombre = nombre;
this.salarioBase = salarioBase;
}
calcularSalario() {
return this.salarioBase;
}
toString() {
return `${this.nombre} — Salario: $${this.calcularSalario()}`;
}
}
class Gerente extends Empleado {
constructor(nombre, salarioBase, bono) {
super(nombre, salarioBase);
this.bono = bono;
}
// Extiende la lógica del padre en lugar de reemplazarla
calcularSalario() {
const base = super.calcularSalario(); // ← llama al padre
return base + this.bono;
}
}
const emp = new Empleado("Carlos", 3000);
const ger = new Gerente("Diana", 5000, 1500);
console.log(emp.toString()); // "Carlos — Salario: $3000"
console.log(ger.toString()); // "Diana — Salario: $6500"class Empleado {
nombre: string;
salarioBase: number;
constructor(nombre: string, salarioBase: number) {
this.nombre = nombre;
this.salarioBase = salarioBase;
}
calcularSalario(): number {
return this.salarioBase;
}
toString(): string {
return `${this.nombre} — Salario: $${this.calcularSalario()}`;
}
}
class Gerente extends Empleado {
bono: number;
constructor(nombre: string, salarioBase: number, bono: number) {
super(nombre, salarioBase);
this.bono = bono;
}
override calcularSalario(): number {
const base = super.calcularSalario(); // ← llama al padre
return base + this.bono;
}
}
const emp = new Empleado("Carlos", 3000);
const ger = new Gerente("Diana", 5000, 1500);
console.log(emp.toString()); // "Carlos — Salario: $3000"
console.log(ger.toString()); // "Diana — Salario: $6500"Carlos — Salario: $3000
Diana — Salario: $6500