JavaScript Extendiendo Clases Integradas
Aprende a extender clases integradas como Array, Map, Set y Error en JavaScript para crear colecciones y tipos de error especializados.
Las clases integradas como Array, Map, Set y Error son clases regulares bajo el capó, por lo que puedes usar extends con ellas igual que con cualquier clase que escribas tú mismo. La subclase hereda todos los métodos del padre y puede agregar nuevos métodos, sobreescribir comportamientos o almacenar estado adicional. Esta es una forma limpia de construir una colección especializada (un array que sabe cómo sumarse a sí mismo, un map con valores predeterminados) o un tipo de error específico de dominio — sin tocar el prototipo global, que es la alternativa más arriesgada descrita en JavaScript: Prototipos Nativos.
Este capítulo cubre la sintaxis básica, cómo los métodos integrados devuelven instancias de tu subclase, cómo Symbol.species te permite controlar eso, cómo extender Error para tipos de error personalizados y las advertencias que vale la pena conocer antes de recurrir a esta técnica. Si eres nuevo en extends y super, lee primero JavaScript: Herencia de Clases.
Sintaxis y Ejemplo Básico
La sintaxis es idéntica a la de extender cualquier otra clase:
class CustomClass extends BuiltInClass {
// New methods and properties to extend the built-in class
}Si tu subclase define un constructor, debes llamar a super() antes de usar this. Esto permite que la clase padre inicialice su estado interno y las estructuras nativas correctamente — para Array y Map, ese estado interno es lo que los hace funcionar en absoluto.
Extendamos la clase Array con un método que sume todos los elementos:
La instancia se comporta como un array completo — la indexación, length, la iteración y todos los métodos de Array funcionan — además del método sum() que agregaste.
⚠️ Nota: Al extender Array, ten en cuenta que la propiedad length se comporta de forma especial en JavaScript. En algunos entornos, length puede no sincronizarse automáticamente con el tamaño real del array cuando se utilizan ciertos métodos nativos. Prueba exhaustivamente o considera la composición si el seguimiento preciso de la longitud es fundamental.
Los Métodos Integrados Devuelven Instancias de Tu Subclase
Esta es la parte que hace que extender Array sea genuinamente poderoso: métodos como map, filter y slice que devuelven un nuevo array devuelven una instancia de tu subclase, no un Array simple. Eso significa que el nuevo array todavía tiene tus métodos personalizados.
Internamente, el motor decide qué constructor usar a través de un getter estático especial llamado Symbol.species. Por defecto, Symbol.species devuelve la propia subclase, que es por qué filter produjo un ExtendedArray anteriormente.
Controlando el Tipo de Retorno con Symbol.species
A veces deseas lo contrario: que métodos como map y filter devuelvan arrays simples, mientras que new ExtendedArray(...) siga dándote tu subclase. Sobreescribe Symbol.species para que apunte de nuevo a la clase base:
Ahora arr es un PowerArray con tu método isEmpty(), pero filter devuelve un Array simple que ya no tiene ese método. Usa esto cuando tu subclase agrega estado en su constructor que los arrays derivados no deben heredar. Symbol.species también es respetado por Map, Set, ArrayBuffer y Promise.
Mejorando la Clase String
La clase String es otro objeto integrado fundamental que se puede extender con ayudantes adicionales de manipulación de cadenas.
Agregando una Función de Inversión
⚠️ Nota: Extender String generalmente se desaconseja debido a las peculiaridades de coerción primitivo-a-objeto de JavaScript, que pueden causar comportamientos inesperados con los métodos nativos. Para la manipulación de cadenas, prefiere las funciones de utilidad o la composición sobre la herencia.
Personalizando la Clase Map
La clase Map en JavaScript representa una colección de elementos de datos con clave, ofreciendo un medio de almacenamiento de datos más avanzado y flexible en comparación con los objetos. Extender la clase Map nos permite introducir comportamientos más especializados.
Implementando un Valor Predeterminado
Extendiendo Map para devolver un valor predeterminado si la clave no existe. Observa cómo el método get sobreescrito llama a super.get(key) para acceder a la búsqueda real de Map:
Extendiendo la Clase Error
Un uso común y práctico de esta característica es crear tipos de error personalizados. Crear una subclase de Error te da un error con nombre que puedes identificar con instanceof, mientras sigue siendo un Error real (por lo que lleva un message, un stack y funciona con try...catch).
Establecer this.name es importante — controla cómo se imprime el error y te permite distinguir tu tipo de error de uno genérico. Para un tratamiento más profundo, incluyendo cómo construir una jerarquía de clases de error, consulta JavaScript: Errores Personalizados, Extendiendo Error.
Buenas Prácticas y Consideraciones
Si bien extender las clases integradas abre un mundo de posibilidades, es fundamental adherirse a las buenas prácticas para garantizar la mantenibilidad y compatibilidad del código.
- Evita Sobreescribir Métodos Existentes: Extender las clases integradas agregando nuevos métodos es generalmente seguro. Sin embargo, sobreescribir métodos existentes puede dar lugar a comportamientos impredecibles y problemas de compatibilidad.
- Úsalo para Necesidades Específicas: Extiende las clases integradas cuando haya un beneficio o necesidad claros. Evita extensiones innecesarias que podrían complicar tu base de código.
- Prefiere la Composición o las Funciones de Utilidad: En el JavaScript moderno, extender las clases integradas a menudo es innecesario. Usar funciones auxiliares o composición generalmente proporciona resultados más limpios y predecibles sin modificar los internos de las subclases nativas.
- Documenta las Extensiones Claramente: Asegúrate de que cualquier extensión a las clases integradas esté bien documentada dentro de tu base de código para evitar confusión entre otros desarrolladores.
Los Métodos Estáticos También se Heredan
Cuando usas extends con una clase integrada, la subclase también hereda los métodos estáticos del padre. Por lo tanto, ExtendedArray.from(...) y ExtendedArray.isArray(...) están disponibles, y los métodos de fábrica estáticos como Array.from producen instancias de la subclase. Esto refleja la herencia de clases normal — consulta JavaScript: Propiedades y Métodos Estáticos para ver cómo se heredan los miembros estáticos.
Conclusión
Extender las clases integradas es una forma limpia de agregar comportamiento enfocado a los objetos nativos — un array sumador, un map con valores predeterminados, un error con nombre — sin modificar los prototipos globales. La idea clave a recordar es que los métodos integrados derivados devuelven instancias de tu subclase por defecto, y Symbol.species es el mecanismo para cambiar eso. Recurre a la herencia cuando exista una verdadera relación "es-un"; de lo contrario, prefiere las funciones de utilidad simples o la composición.