Aprende leyendo en orden

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)
El árbol de herencia de Book a EBook
class Bookconstructor,describebook solo puedellamar a describeclass EBookextends BookEBook solodefine downloadInstancia creadacon new EBook(...)Puede llamar adescribe y adownload
EBook solo define download, pero también puedes llamar a describe. Al revés no funciona: book no puede llamar a download.

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.

Vas a mostrar un registro por cada contraseña que restablece un administrador. User y targets ya están declarados.

① Define AdminUser, que hereda de User.

② Agrega resetPassword, que devuelve un registro con la descripción del propio administrador y el nombre del otro usuario.

③ Crea un administrador y muestra un registro para cada persona de targets.

④ Muestra si el administrador también es una instancia de User.

(Si se ejecuta correctamente, aparecerá una explicación.)

Editor JavaScript / TypeScript

Ejecutar el código para ver el resultado

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
¿Se llama a super() antes de this?
Primero se escribethis.fileSizesuper() aúnno se llamóFalla en la líneaque usa thisReferenceErroral hacer newPrimero se escribesuper(title,price)Book asignatitle y pricethis.fileSizerecibe 12ebook tienelos 3 valores
En la fila de arriba, usar this antes de super() lanza un error y no se crea ninguna instancia. Solo puedes usar this cuando super() ya terminó.

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.

Vas a listar los permisos de cada administrador en un panel de administración. User y adminInput ya están declarados.

① Define AdminUser, que hereda de User y recibe un nombre, un correo y un array de permisos.

② Asigna el nombre y el correo en el constructor de User.

③ Crea un administrador a partir de adminInput y muestra su nombre junto con el detalle de los permisos.

④ Muestra las claves en orden y comprueba dónde aparece el status definido en User.

Editor JavaScript / TypeScript

Ejecutar el código para ver el resultado

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)
Dónde busca JavaScript el método que llamas
EBook sobrescribesolo shippingFeebook.shippingFee()ebook.shippingFee()ebook.describe()Empieza en Book,lo halla en BookEmpieza en EBook,lo halla en EBookNo está en EBook,lo halla en Book2640 es menor que3000: da 5000, sin llegara BookEstadística 101:2200 yenes
La búsqueda empieza en la clase que creó la instancia. Si el nombre está en EBook, el método de Book con el mismo nombre no se ejecuta.

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.

En un foro interno, vas a decidir si se muestra el botón de editar en cada publicación. User y posts ya están declarados.

① Define AdminUser, que hereda de User, y sobrescribe la comprobación para que un administrador pueda editar cualquier publicación.

② Crea un usuario común, «Carlos», y una administradora, «Ana».

③ Para cada publicación, muestra si cada uno puede editarla, con el formato «Publicación N: Carlos X / Ana Y».

Editor JavaScript / TypeScript

Ejecutar el código para ver el resultado

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)
Qué se ejecuta dentro de ebook.describe(), en orden
Se empieza aejecutarebook.describe()this es ebook(fileSize es 12)super.describe()ejecuta eldescribe de Bookthis sigue siendoebook: Estadística101: 2200 yenesAgrega (digital,12 MB) al valordevueltoDa Estadística101: 2200 yenes(digital, 12 MB)
El describe de Book se ejecuta a mitad del describe de EBook y luego regresa. El código que arma el título y el precio solo está en Book.

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.

SintaxisDónde vaQué se ejecuta
class EBook extends BookDeclaración de claseHereda constructor y métodos de Book
super(title, price)Constructor hijo, antes de thisEl constructor de Book
Método homónimo del padreCuerpo de la clase hijaEl de EBook, en sus instancias
super.describe()Dentro de un método hijoEl 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..

En una herramienta interna, vas a aplicar una regla de contraseñas más estricta solo a los administradores. User, AdminUser, admin, member y passwords ya están declarados.

① Agrega checkPassword a AdminUser. Debe ejecutar primero la comprobación de User y, si la pasa, comprobar además que tenga al menos 12 caracteres.

② Comprueba las contraseñas en orden como administradora y muestra los resultados.

③ Comprueba la segunda contraseña como usuario común y muestra el resultado.

Editor JavaScript / TypeScript

Ejecutar el código para ver el resultado
QUIZ

Verificación de conocimientos

Responde cada pregunta una a una.

Pregunta 1¿Qué pasa si haces new de un EBook que asigna this.fileSize antes de super()?

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?