Apariencia
📗 Clase 10 — Extender métodos nativos en TypeScript
TypeScript · 2026-08-05 · Carpeta:
02-Ejercicios/Extends⬅️ Volver al índice de clases
🎯 Qué aprendí
- Qué significa "extender" un método nativo usando
extendsdentro de un genérico (no es herencia de clases — es una restricción de forma). - Cómo restringir
<T>para que solo acepte tipos con una propiedad concreta (T extends { length: number }). - Cómo enriquecer un método nativo (
.length) agregando información extra (typeof) sin perder la lógica original. - Qué error da TypeScript exactamente cuando el tipo que le pasas no cumple la restricción, y qué señales muestra VS Code antes de compilar.
- Cuándo tiene sentido esta técnica (y cuándo es solo "envolver por envolver").
📖 PARTE TEÓRICA
🧩 1. ¿Qué significa "extender" un método nativo aquí?
No se trata de heredar entre clases propias (eso ya lo viste en la Clase 8). Aquí "extender" significa apoyarte en extends dentro de una función genérica para que tu función pueda usar con confianza una propiedad que ya traen ciertos tipos nativos de TypeScript — en este caso, .length (que tienen los string y los arrays, pero no los number ni los boolean).
🧪 Entrevista: ¿Qué hace
extendsdentro de<T extends Forma>? Restringe qué tipos puede tomarT: solo acepta valores que cumplan esa forma. Así tu función puede acceder a esa propiedad sin que TypeScript se queje.
🖼️ 2. Esquema visual — anatomía de la restricción

Resumen del diagrama:
| Parte de la firma | Qué hace |
|---|---|
function obtenerLongitud | El nombre de la función |
<T extends {length: number}> | Define el genérico T, pero restringido: T puede ser cualquier tipo, siempre que tenga una propiedad length de tipo number (strings, arrays, u objetos con length) |
(obj: T) | El parámetro obj es del tipo T que ya sabemos que tiene length |
: number | La función siempre devuelve un number |
📝 Ojo con la lectura:
T extends {length: number}no significa "T extiende a la funciónlength". Significa "T debe cumplir la forma{length: number}" — es una restricción de forma, no una herencia.
🔤 3. El punto de partida: .length nativo
Antes de escribir nada propio, confirmas que TypeScript ya te da .length gratis en strings:
typescript
let nombre: string = "Admin Canto Rondon";
console.log(nombre.length); // 18 — .length ya viene incorporado en string🏗️ 4. Construir obtenerLongitud<T> con la restricción
La función recibe un valor genérico y devuelve su longitud — pero solo compila si el T que le pasas tiene .length:
typescript
function obtenerLongitud<T extends {length: number}>(obj: T): number {
return obj.length;
}
console.log(obtenerLongitud("hola")); // 4
console.log(obtenerLongitud([1, 2, 3, 4, 5])); // 5💡 En esta primera versión, la función solo repite lo que
.lengthya hacía sola. Todavía no aporta nada nuevo — eso viene en el siguiente paso.
✨ 5. Enriquecer la función con typeof
La ganancia real aparece cuando la función devuelve más información de la que .length da por sí solo:
typescript
function obtenerLongitud<T extends {length: number}>(obj: T): number {
let longitud: number = obj.length;
let tipo: string = typeof obj;
console.log(`El tipo de datos es: ${tipo} y la longitud es: ${longitud}`);
return obj.length;
}
obtenerLongitud("hola"); // El tipo de datos es: string y la longitud es: 4
obtenerLongitud([1, 2, 3, 4, 5]); // El tipo de datos es: object y la longitud es: 5⚠️ Ojo con
typeofen arrays: da"object", no"array"— es una rareza de JavaScript (los arrays son técnicamente un tipo de objeto). Si quisieras distinguir un array de verdad, se usaArray.isArray(obj)en vez detypeof.
🚫 6. Qué pasa si el tipo NO cumple la restricción
Si intentas pasar un number puro (que no tiene .length), TypeScript lo rechaza:
typescript
console.log(obtenerLongitud(42)); // ❌VS Code te avisa antes de compilar (subrayado rojo en la línea, el archivo en rojo en el explorador, y el minimapa lateral resalta el renglón). Si aun así corres tsc, el compilador te da el error exacto, verificado en terminal:
text
error TS2345: Argument of type 'number' is not assignable to parameter of type '{ length: number; }'.💡 La solución no es "forzar" el tipo — es reconocer que
42no tiene.length, así que no le corresponde entrar a esta función. El error está haciendo su trabajo.
🧭 7. ¿Cuándo conviene esta técnica?
No se trata de envolver cada método nativo del lenguaje "porque sí". Tiene sentido cuando necesitas información adicional o lógica repetida que el método original no te da:
- Mostrar tipo y tamaño de un dato en logs de depuración.
- Validar entradas antes de procesarlas.
- Estandarizar respuestas que combinan datos nativos con metadatos propios.
💡 Regla práctica: si tu función termina haciendo exactamente lo mismo que el método nativo (paso 4 de esta clase), no aporta nada. Si le suma algo (paso 5), ya vale la pena.
🔬 8. Otra forma de "extender" (bonus — no es lo que enseña esta clase)
Existe una segunda técnica, más profunda, para literalmente agregarle un método nuevo a un tipo nativo (no solo restringir un genérico): declaration merging + asignar al .prototype. La exploramos aparte mientras esperábamos el material real de esta clase, y queda documentada aquí como referencia — no reemplaza lo de arriba, lo complementa.
typescript
interface String {
invertir(): string;
}
String.prototype.invertir = function (): string {
return this.split("").reverse().join("");
};
console.log("Admin Canto Rondon".invertir()); // nodnoR otnaC nimdA⚠️ Si te saltas la segunda línea (asignar al
.prototype) y solo declaras lainterface, compila sin error pero truena al ejecutar (TypeError: ... is not a function) — verificado en terminal. Lainterfacesola es solo una promesa de tipo; el método real hay que agregarlo a mano.
💻 PARTE PRÁCTICA
Archivo trabajado: 02-Ejercicios/Extends/main.ts
bash
cd 02-Ejercicios/Extends
tsc main.ts && node main.jsContenido real verificado:
typescript
let nombre: string = "Admin Canto Rondon";
console.log(nombre.length)
function obtenerLongitud<T extends {length: number}>(obj: T): number{
let longitud: number = obj.length;
let tipo: string = typeof obj;
console.log(`El tipo de datos es: ${tipo} y la longitud es: ${longitud}`);
return obj.length;
}
console.log(obtenerLongitud("Hola mundo"));
console.log(obtenerLongitud([1,2,3,4,5]));Salida real verificada:
$ tsc main.ts
$ node main.js
18
El tipo de datos es: string y la longitud es: 10
10
El tipo de datos es: object y la longitud es: 5
5Todo verificado y corriendo. ✅
📝 Nota: esta carpeta no tiene
tsconfig.jsona propósito — sigue el estilo del profesor (tsc archivo.ts && node archivo.jsdirecto). Si le agregaras uno, tendrías que compilar contsca secas (ver la clase de Genéricos para el porqué).
🏋️ EJERCICIOS CON SOLUCIÓN
Ejercicio 1 — obtenerTipo<T> (sin restricción)
Función genérica sin extends que reciba cualquier valor y devuelva su typeof. Sirve para contrastar: sin restricción, T acepta literalmente cualquier cosa.
Ver solución
typescript
function obtenerTipo<T>(valor: T): string {
return typeof valor;
}
console.log(obtenerTipo("hola"));
console.log(obtenerTipo(42));
console.log(obtenerTipo([1, 2, 3]));Terminal esperada:
$ tsc obtener-tipo.ts
$ node obtener-tipo.js
string
number
objectEjercicio 2 — sumarPesos<T extends { peso: number }>
Función restringida que reciba un array de objetos con propiedad peso: number y devuelva la suma total de todos los pesos.
Ver solución
typescript
interface ConPeso {
peso: number;
}
function sumarPesos<T extends ConPeso>(items: T[]): number {
return items.reduce((total, item) => total + item.peso, 0);
}
const productos = [
{ nombre: "Caja", peso: 5 },
{ nombre: "Libro", peso: 2 },
{ nombre: "Laptop", peso: 3 },
];
console.log(sumarPesos(productos));Terminal esperada:
$ tsc sumar-pesos.ts
$ node sumar-pesos.js
10Ejercicio 3 — mostrarId<T extends { id: number }>
Función restringida que reciba cualquier objeto con id: number e imprima ese id. Prueba con un objeto que tenga más propiedades además de id.
Ver solución
typescript
interface ConId {
id: number;
}
function mostrarId<T extends ConId>(item: T): void {
console.log(`ID recibido: ${item.id}`);
}
mostrarId({ id: 7, nombre: "Producto A" });Terminal esperada:
$ tsc mostrar-id.ts
$ node mostrar-id.js
ID recibido: 7💡 Le pasaste un objeto con
idynombre, y no dio error — la restricción solo pide "al menos" esa forma, de más no molesta.
Ejercicio 4 — Forzar el error a propósito
Antes de correrlo: ¿qué error esperas si le pasas 42 a obtenerLongitud<T extends {length: number}>? Anticipa la respuesta y después verifícala.
Ver solución
typescript
function obtenerLongitud<T extends { length: number }>(obj: T): number {
return obj.length;
}
console.log(obtenerLongitud(42)); // ❌ forzado a propósitoTerminal esperada:
$ tsc error-forzado.ts
error TS2345: Argument of type 'number' is not assignable to parameter of type '{ length: number; }'.💡
42no tiene.length— por eso no cumple la restricción{length: number}. El error es la restricción funcionando como debe.
Ejercicio 5 — Restricción con DOS propiedades
Función describirProducto<T extends { nombre: string; precio: number }> que reciba un objeto con esas dos propiedades y devuelva un texto describiéndolo.
Ver solución
typescript
function describirProducto<T extends { nombre: string; precio: number }>(item: T): string {
return `${item.nombre} cuesta S/ ${item.precio}`;
}
console.log(describirProducto({ nombre: "Teclado", precio: 150 }));Terminal esperada:
$ tsc describir-producto.ts
$ node describir-producto.js
Teclado cuesta S/ 150💡 La restricción puede pedir varias propiedades a la vez, no solo una —
Tdebe cumplir la forma completa{ nombre: string; precio: number }.
Ejercicio 6 — Restricción aplicada a un array
Función filtrarActivos<T extends { activo: boolean }>(items: T[]): T[] que reciba un array de objetos y devuelva solo los que tienen activo: true.
Ver solución
typescript
function filtrarActivos<T extends { activo: boolean }>(items: T[]): T[] {
return items.filter((item) => item.activo);
}
const usuarios = [
{ nombre: "Ana", activo: true },
{ nombre: "Luis", activo: false },
{ nombre: "Marta", activo: true },
];
console.log(filtrarActivos(usuarios));Terminal esperada:
$ tsc filtrar-activos.ts
$ node filtrar-activos.js
[ { nombre: 'Ana', activo: true }, { nombre: 'Marta', activo: true } ]Ejercicio 7 — Calcular un promedio con genérico restringido
Función promedioDe<T extends { valor: number }>(items: T[]): number que reciba un array de objetos con propiedad valor y devuelva el promedio de todos.
Ver solución
typescript
function promedioDe<T extends { valor: number }>(items: T[]): number {
const total = items.reduce((suma, item) => suma + item.valor, 0);
return total / items.length;
}
console.log(promedioDe([{ valor: 10 }, { valor: 20 }, { valor: 30 }]));Terminal esperada:
$ tsc promedio-de.ts
$ node promedio-de.js
20Ejercicio 8 — Extender Array.prototype (declaration merging)
Usa la técnica bonus de la teoría: agrega un método primero() a todos los arrays, que devuelva su primer elemento (o undefined si está vacío).
Ver solución
typescript
interface Array<T> {
primero(): T | undefined;
}
Array.prototype.primero = function <T>(this: T[]): T | undefined {
return this[0];
};
console.log([10, 20, 30].primero());
console.log(["a", "b", "c"].primero());Terminal esperada:
$ tsc array-extend.ts
$ node array-extend.js
10
a💡 Mismo patrón que
String.invertir()de la teoría, aplicado aArray<T>: declaras lainterface(paso 1) y además asignas al.prototype(paso 2) — los dos son obligatorios.
Ejercicio 9 — Extender String.prototype con un método nuevo
Agrega contarVocales(): number a String, que cuente cuántas vocales tiene el texto (usa una expresión regular: this.match(/[aeiouAEIOU]/g)).
Ver solución
typescript
interface String {
contarVocales(): number;
}
String.prototype.contarVocales = function (): number {
const vocales = this.match(/[aeiouAEIOU]/g);
return vocales ? vocales.length : 0;
};
console.log("Admin Canto Rondon".contarVocales());Terminal esperada:
$ tsc contar-vocales.ts
$ node contar-vocales.js
6💡
match()devuelvenullsi no encuentra nada — por eso el? vocales.length : 0(si no,null.lengthtronaría).
Ejercicio 10 — Integrador: restricción + array + varias propiedades
Función idsActivos<T extends { id: number; activo: boolean }>(items: T[]): number[] que reciba un array de objetos y devuelva solo los ids de los que están activos. Combina restricción con dos propiedades (Ejercicio 5) + filtrado de array (Ejercicio 6).
Ver solución
typescript
interface ConIdYActivo {
id: number;
activo: boolean;
}
function idsActivos<T extends ConIdYActivo>(items: T[]): number[] {
return items.filter((item) => item.activo).map((item) => item.id);
}
const tareas = [
{ id: 1, activo: true, titulo: "Estudiar" },
{ id: 2, activo: false, titulo: "Descansar" },
{ id: 3, activo: true, titulo: "Practicar" },
];
console.log(idsActivos(tareas));Terminal esperada:
$ tsc ids-activos.ts
$ node ids-activos.js
[ 1, 3 ]💡
tareastiene una tercera propiedad (titulo) que la restricción ni pide — de más no molesta, solo exige el mínimo (idyactivo).
❓ Preguntas y respuestas (autoevaluación)
1. ¿Qué hace extends dentro de <T extends {length: number}>?
Restringe qué tipos puede tomar
T: solo acepta valores que tengan una propiedadlengthde tiponumber. No es herencia de clases, es una restricción de forma.
2. ¿Por qué obtenerLongitud("hola") funciona pero obtenerLongitud(42) no?
Porque
"hola"(string) tiene.lengthde forma nativa, y42(number) no. La restricción{length: number}solo deja pasar tipos que cumplan esa forma.
3. ¿Qué da typeof sobre un array — "array" u "object"?
"object". Es una particularidad de JavaScript: los arrays son técnicamente un tipo de objeto, ytypeofno los distingue. Para eso existeArray.isArray(obj).
4. Si obtenerLongitud solo hiciera return obj.length, ¿aportaría algo sobre usar .length directo?
No. Recién aporta valor cuando le agregas algo extra (como el
typeof+ el mensaje) que.lengthpor sí solo no te da.
5. ¿Qué avisa VS Code antes incluso de compilar, si le pasas un tipo que no cumple la restricción?
Subraya en rojo la línea del error, marca el archivo en rojo en el explorador, y resalta el renglón en el minimapa lateral.
6. ¿Cuál es la diferencia entre obtenerTipo<T> (Ejercicio 1, sin restricción) y obtenerLongitud<T extends {length: number}>?
obtenerTipo<T>acepta literalmente cualquier valor (no hay restricción).obtenerLongitudsolo acepta valores que tengan.length— por eso uno funciona con42y el otro no.
7. ¿Qué es declaration merging (visto en la sección bonus)?
La técnica de declarar una
interfacecon el mismo nombre que un tipo nativo (String,Array, etc.) para que TypeScript fusione tu declaración con la que ya trae incorporada — así el compilador "sabe" que existe un método nuevo, aunque todavía no lo hayas implementado de verdad.
8. Si declaras interface String { invertir(): string } pero te olvidas de asignar String.prototype.invertir = ..., ¿qué pasa?
Compila sin ningún error (TypeScript confía en la interfaz), pero truena al ejecutar:
TypeError: ... is not a function. La interfaz sola es solo una promesa de tipo; el método real hay que agregarlo al.prototypea mano.
9. En sumarPesos<T extends ConPeso> y mostrarId<T extends ConId> (Ejercicios 2 y 3), ¿qué patrón se repite?
El mismo de toda la clase: restringir
Ta una interfaz con una sola propiedad obligatoria (pesooid). El objeto puede tener más propiedades de las que pide la interfaz — "de más no molesta", solo exige el mínimo.
10. ¿Qué significa exactamente el error Argument of type 'number' is not assignable to parameter of type '{ length: number; }'?
Que el valor que pasaste (
42, de tiponumber) no tiene la forma queTexige (un objeto con propiedadlength: number). No es quenumberesté "mal" — simplemente no cumple el contrato que la función pide para ese parámetro.
📎 Apuntes relacionados
➡️ Siguiente
Módulos en TypeScript — organizar el proyecto sin monolitos (import/export a fondo).