Pregunta 1¿Qué pasa si haces new de un EBook que asigna this.fileSize antes de super()?
Herencia — extends y super
Crea una clase a partir de otra con extends: super() en el constructor de la clase hija, la sobrescritura y super.método().
Supón que una librería en línea necesita, además de su clase Book para libros impresos, una clase para libros electrónicos. Lo único que cambia es que los libros electrónicos se pueden descargar y no pagan envío, pero con lo que sabes hasta ahora tendrías que volver a escribir también la parte que maneja el título y el precio.
Este artículo trata extends, que construye una clase sobre otra existente, y super, que llama al código de la clase de la que partiste.
Reutilizar código sin copiarlo — extends
Los libros electrónicos también necesitan un constructor que reciba el título y el precio, y un método describe que devuelva «título: precio yenes». Si copias el cuerpo de Book en una clase nueva para libros electrónicos, las mismas líneas quedan en dos lugares. Si después cambias el formato y olvidas actualizar una de las copias, cada pantalla termina mostrando un formato distinto.
La herencia (un mecanismo que permite que otra clase use el constructor y los métodos de una clase existente) se escribe class EBook extends Book. A Book se le llama clase padre, y a EBook, clase hija.
El extends Error que escribiste en el artículo sobre throw y errores personalizados también es herencia.
class Book {
constructor(title, price) {
this.title = title;
this.price = price;
}
describe() { return `${this.title}: ${this.price} yenes`; }
}
// Parte de Book y agrega un método que solo tienen los libros electrónicos
class EBook extends Book {
download() { return `Descargando ${this.title}`; }
}
const ebook = new EBook("Estadística 101", 2200); // Los argumentos van al constructor de Book
console.log(ebook.describe()); // Estadística 101: 2200 yenes
console.log(ebook.download()); // Descargando Estadística 101
const book = new Book("Estadística 101", 2640);
console.log(typeof book.download); // undefined (la clase padre no tiene los métodos de la hija)
Dentro de ebook.describe(), this es ebook, así que el this.title escrito en Book lee el título que pasaste a new EBook. Y fue el constructor de Book, al recibir esos argumentos, el que guardó el título en ebook.
Agregar valores en una clase hija — super() y el constructor
Ahora los libros electrónicos también deben guardar el tamaño del archivo, fileSize, además del título y el precio. Para recibir el tamaño, EBook necesita su propio constructor. Pero en cuanto lo escribes, el constructor de Book ya no se llama automáticamente, y si lo dejas así, new lanza un ReferenceError.
super() (la forma de llamar al constructor de la clase padre desde el constructor de una clase hija) recibe los argumentos que se le pasan al padre, como en super(title, price);.
En una clase hija, el this lo prepara el constructor del padre durante super(), así que llámalo antes de cualquier línea que use this.
class Book {
constructor(title, price) {
this.title = title;
this.price = price;
}
}
class EBook extends Book {
constructor(title, price, fileSize) {
// Si escribes this.fileSize = fileSize; antes de esta línea, new lanza un ReferenceError
super(title, price); // El constructor de Book asigna title y price
this.fileSize = fileSize; // Luego agrega el valor que solo tiene EBook
}
}
const ebook = new EBook("Estadística 101", 2200, 12);
console.log(ebook.title); // Estadística 101
console.log(ebook.fileSize); // 12
console.log(Object.keys(ebook).join(", ")); // title, price, fileSize
Las claves salen en el orden title, price, fileSize porque Book asignó sus valores primero, dentro de super(). La fila de arriba falla con ReferenceError: Must call super constructor in derived class before accessing 'this' …, donde «derived class» (clase derivada) se refiere a la clase hija.
Una clase hija no puede leer los campos # del padre
Si Book tiene un campo privado #price, super() igual le asigna su valor, pero escribir this.#price en el cuerpo de EBook es un SyntaxError. Un nombre con # solo se puede usar dentro de la clase que lo declara, así que la clase hija tiene que leerlo a través de un getter definido en el padre.
Cambiar el costo de envío en una clase hija — sobrescritura
Si agregas a Book un método shippingFee, los libros electrónicos pagan el mismo envío que los impresos. Podrías dejarlo en 0 yenes solo para los electrónicos revisando el tipo de libro con if dentro de Book, pero entonces la clase padre necesitaría otra rama cada vez que agregaras una clase hija.
La sobrescritura (override: escribir en la clase hija un método con el mismo nombre que uno del padre, para que las instancias de la hija ejecuten esa versión) consiste simplemente en volver a escribir shippingFee en el cuerpo de EBook. Cuando llamas a un método, JavaScript busca el nombre empezando por la clase que creó la instancia y, si no lo encuentra ahí, pasa a la clase padre.
class Book {
constructor(title, price) {
this.title = title;
this.price = price;
}
describe() { return `${this.title}: ${this.price} yenes`; }
shippingFee() { return this.price >= 3000 ? 0 : 500; } // Envío gratis desde 3000 yenes
}
class EBook extends Book {
shippingFee() { return 0; } // Se sobrescribe con el mismo nombre (no hay nada que enviar)
}
const book = new Book("Estadística 101", 2640);
const ebook = new EBook("Estadística 101", 2200);
console.log(book.shippingFee()); // 500
console.log(ebook.shippingFee()); // 0 (se ejecuta el shippingFee de EBook)
console.log(ebook.describe()); // Estadística 101: 2200 yenes (se ejecuta el describe de Book)
El shippingFee de EBook no usa el cálculo de Book, así que aunque cambies las reglas de envío de Book, los libros electrónicos siguen en 0 yenes. En la siguiente sección verás cómo llamar al código del padre y agregar solo lo que cambia.
Agregar algo al código del padre — super.nombreDelMétodo()
Ahora quieres que la descripción de los libros electrónicos termine con «(digital, 12 MB)». Si sobrescribes describe en EBook y copias el código que arma «título: precio yenes», la próxima vez que cambies el formato de Book, las descripciones de los libros electrónicos se quedarán con el formato viejo.
Con super.nombreDelMétodo() (la forma de llamar a un método definido en la clase padre desde un método de la clase hija), super.describe() devuelve el valor del describe de Book. La clase hija solo tiene que agregar a esa cadena los datos del libro electrónico.
class Book {
constructor(title, price) {
this.title = title;
this.price = price;
}
describe() { return `${this.title}: ${this.price} yenes`; }
}
class EBook extends Book {
constructor(title, price, fileSize) {
super(title, price);
this.fileSize = fileSize;
}
// Agrega los datos del libro electrónico al valor que devuelve el describe de Book
describe() { return `${super.describe()} (digital, ${this.fileSize} MB)`; }
}
const ebook = new EBook("Estadística 101", 2200, 12);
console.log(ebook.describe()); // Estadística 101: 2200 yenes (digital, 12 MB)
super.describe() es una expresión que se evalúa al valor que devuelve Book, así que si prefieres poner los datos del libro electrónico al principio, basta con moverla dentro del template literal. La tabla de abajo resume la sintaxis de herencia de este artículo y qué se ejecuta en cada caso.
| Sintaxis | Dónde va | Qué se ejecuta |
|---|---|---|
| class EBook extends Book | Declaración de clase | Hereda constructor y métodos de Book |
| super(title, price) | Constructor hijo, antes de this | El constructor de Book |
| Método homónimo del padre | Cuerpo de la clase hija | El de EBook, en sus instancias |
| super.describe() | Dentro de un método hijo | El describe de Book |
Sin super, el método se llama a sí mismo sin fin
Si en el describe de la clase hija escribes this.describe() en lugar de super.describe(), el método se llama a sí mismo una y otra vez hasta lanzar RangeError: Maximum call stack size exceeded. Para llamar al código del padre desde un método que sobrescribiste, usa super..
Verificación de conocimientos
Responde cada pregunta una a una.
Pregunta 2Si tanto Book como EBook tienen un método shippingFee, ¿cuál se ejecuta al llamar a ebook.shippingFee()?
Pregunta 3Dentro del describe de EBook, ¿cómo obtienes el valor que devuelve el describe de Book?