Herencia de Clases en JavaScript
Aprende la herencia de clases en JavaScript con extends y super: la cadena de prototipos, sobrescritura de métodos, llamadas al padre y clases nativas.
Introducción a la Herencia de Clases en JavaScript
La herencia de clases es un concepto fundamental de la programación orientada a objetos que permite que una clase herede propiedades y métodos de otra clase. En JavaScript, la herencia de clases se implementa mediante la palabra clave extends, que proporciona una forma de crear una clase derivada que hereda de una clase base.
Para más información sobre la sintaxis básica, consulta JavaScript: Clases y Sintaxis Básica.
Este capítulo explica cómo crear clases derivadas, sobrescribir métodos y propiedades, llamar al padre con super, extender clases nativas como Array y heredar métodos estáticos — además de la cadena de prototipos que hace que todo funcione internamente.
Cómo Funciona la Herencia: La Cadena de Prototipos
La palabra clave class es azúcar sintáctico sobre la herencia prototípica de JavaScript. Cuando escribes class Circle extends Shape, el motor establece dos enlaces:
Circle.prototype.__proto__ === Shape.prototype— para que los métodos de instancia se resuelvan subiendo por la cadena.Circle.__proto__ === Shape— para que los métodos estáticos también se hereden.
Cuando lees una propiedad o llamas a un método sobre un objeto, el motor busca primero en el propio objeto. Si no lo encuentra allí, recorre la cadena de prototipos — a Circle.prototype, luego a Shape.prototype, luego a Object.prototype, y finalmente a null — deteniéndose en la primera coincidencia. Esto explica exactamente por qué una instancia de Circle puede llamar a un método definido en Shape.
Object.getPrototypeOf(obj) devuelve el siguiente eslabón de la cadena (la forma estándar y preferida de inspeccionarla). El acceso heredado obj.__proto__ apunta al mismo objeto. Entender esta cadena explica todo lo que viene a continuación: la sobrescritura funciona porque un prototipo más cercano oculta uno más lejano, y super funciona porque salta explícitamente al prototipo del padre.
Crear una Clase Derivada
Para crear una clase que herede de otra, usamos la palabra clave extends:
En este ejemplo, Circle extiende Shape, lo que significa que hereda las propiedades y métodos de Shape, y al mismo tiempo proporciona métodos propios. Observa que Circle no define su propio constructor. En JavaScript, las clases derivadas sin un constructor explícito llaman automáticamente a super() con los mismos argumentos que se pasan al constructor de la clase derivada.
Sobrescribir Métodos
Las clases derivadas pueden sobrescribir métodos de sus clases base para proporcionar comportamiento específico a la subclase.
Aquí, Circle sobrescribe el método print para reflejar su tipo específico.
Llamar a Métodos del Padre con super
También puedes llamar al método de una clase padre desde la clase derivada usando super.methodName(). Esto es útil cuando quieres extender el comportamiento del padre en lugar de reemplazarlo completamente.
Aquí, super.print() ejecuta la lógica del padre antes de agregar la salida específica de la subclase.
Una Sobrescritura Realista: Calcular el Área
La sobrescritura brilla cuando cada subclase necesita un comportamiento genuinamente diferente. Aquí, una clase base Shape define la interfaz compartida, y cada subclase sobrescribe area() con su propio cálculo real. Llamar a area() sobre un Circle resuelve a Circle.prototype.area, que oculta la versión base.
Observa que describe() solo está definido en Shape, pero llama a this.area() y obtiene la implementación de la subclase. Eso es la cadena de prototipos en acción: this siempre hace referencia a la instancia real, por lo que la búsqueda de métodos comienza desde Circle o Rectangle.
Sobrescribir Propiedades y Leer super.prop
super no se limita a los métodos — puedes leer una propiedad definida en el prototipo padre con super.prop, lo cual es útil cuando una subclase quiere construir sobre el getter del padre en lugar de reemplazarlo.
Acceder al Constructor del Padre: la Palabra Clave super
Cuando una clase extiende otra, la función constructora de la clase derivada necesita llamar al constructor del padre usando super() antes de poder usar this. Así es como se usa super en los constructores para garantizar que la clase padre sea inicializada:
Por Qué super() Debe Ejecutarse Antes que this
En una clase derivada, el objeto instancia no se crea hasta que super() se ejecuta — esa es la tarea del constructor padre. Hasta entonces, this se encuentra en un estado no inicializado, por lo que tocarlo (leer, asignar o incluso devolver el objeto implícitamente) lanza un ReferenceError. Esta es una regla del lenguaje, no una preferencia de estilo.
Una regla relacionada: si una clase derivada define un constructor, debe llamar a super() en algún punto antes de terminar, o se lanzará el mismo ReferenceError. (Una clase derivada sin constructor explícito está bien — JavaScript inserta constructor(...args) { super(...args); } por ti.) Una vez que super() retorna, this está completamente inicializado y listo para usarse.
Heredar Métodos Estáticos
Los miembros estáticos pertenecen a la clase en sí, no a las instancias. Dado que extends también enlaza Circle.__proto__ con Shape, las clases derivadas heredan los métodos estáticos y pueden llamarlos directamente. Para más información sobre cómo declararlos, consulta Propiedades y Métodos Estáticos de JavaScript.
Dentro de un método estático, this hace referencia a la clase sobre la que fue invocado, por lo que Shape.create llamado como Circle.create construye un Circle.
Herencia Multinivel
Las cadenas pueden tener más de dos niveles de profundidad. La búsqueda de métodos simplemente sube más por la cadena de prototipos, y super siempre hace referencia al prototipo un nivel por encima de la clase donde se define el método.
Extender Clases Nativas
Puedes extender clases nativas como Array, Error o Map para crear versiones especializadas que conserven todo el comportamiento nativo mientras añaden el tuyo propio.
El problema con Symbol.species. Los métodos como map, filter y slice devuelven una nueva colección. Por defecto, devuelven una instancia de tu subclase (MyArray), no un Array simple — lo cual suele estar bien, pero puede sorprender al código que espera un Array real. Puedes volver a arrays simples sobrescribiendo el getter estático Symbol.species.
Para combinar comportamiento de múltiples fuentes (JavaScript no tiene herencia múltiple), consulta JavaScript Mixins.
Resumen
Puntos clave:
- Usa
extendspara derivar una clase; enlaza tanto la cadena de prototipos (métodos de instancia) como la clase en sí (métodos estáticos) con el padre. - La búsqueda de métodos y propiedades recorre la cadena de prototipos, deteniéndose en la primera coincidencia — por eso una definición más cercana sobrescribe una más lejana. Inspecciona la cadena con
Object.getPrototypeOf(). - En un constructor derivado, llama a
super()antes dethis. Hasta quesuper()se ejecute, la instancia no está inicializada y cualquier uso dethislanza unReferenceError. - Accede al comportamiento del padre explícitamente con
super.method()osuper.prop, incluso cuando lo hayas sobrescrito. - Puedes extender clases nativas como
Array— solo ten en cuenta el comportamiento deSymbol.speciescuando los métodos devuelvan nuevas colecciones.
Próximos pasos:
- Herencia Prototípica de JavaScript — el mecanismo sobre el que se construyen las clases.
- Propiedades y Métodos Estáticos de JavaScript — declarar y heredar miembros estáticos.
- JavaScript Mixins — reutilizar comportamiento entre clases no relacionadas sin una cadena de herencia única.