Skip to content

📗 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 extends dentro 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 extends dentro de <T extends Forma>? Restringe qué tipos puede tomar T: 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

Explicación de function obtenerLongitud con genérico restringido por extends

Resumen del diagrama:

Parte de la firmaQué hace
function obtenerLongitudEl 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
: numberLa función siempre devuelve un number

📝 Ojo con la lectura: T extends {length: number} no significa "T extiende a la función length". 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 .length ya 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 typeof en 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 usa Array.isArray(obj) en vez de typeof.

🚫 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 42 no 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 la interface, compila sin error pero truena al ejecutar (TypeError: ... is not a function) — verificado en terminal. La interface sola 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.js

Contenido 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:

zsh · main.ts
$ 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
5

Todo verificado y corriendo. ✅

📝 Nota: esta carpeta no tiene tsconfig.json a propósito — sigue el estilo del profesor (tsc archivo.ts && node archivo.js directo). Si le agregaras uno, tendrías que compilar con tsc a 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:

zsh · obtener-tipo.ts
$ tsc obtener-tipo.ts
$ node obtener-tipo.js
string
number
object

Ejercicio 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:

zsh · sumar-pesos.ts
$ tsc sumar-pesos.ts
$ node sumar-pesos.js
10

Ejercicio 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:

zsh · mostrar-id.ts
$ tsc mostrar-id.ts
$ node mostrar-id.js
ID recibido: 7

💡 Le pasaste un objeto con id y nombre, 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ósito

Terminal esperada:

zsh · error-forzado.ts
$ tsc error-forzado.ts
error TS2345: Argument of type 'number' is not assignable to parameter of type '{ length: number; }'.

💡 42 no 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:

zsh · describir-producto.ts
$ 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 — T debe 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:

zsh · filtrar-activos.ts
$ 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:

zsh · promedio-de.ts
$ tsc promedio-de.ts
$ node promedio-de.js
20

Ejercicio 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:

zsh · array-extend.ts
$ tsc array-extend.ts
$ node array-extend.js
10
a

💡 Mismo patrón que String.invertir() de la teoría, aplicado a Array<T>: declaras la interface (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:

zsh · contar-vocales.ts
$ tsc contar-vocales.ts
$ node contar-vocales.js
6

💡 match() devuelve null si no encuentra nada — por eso el ? vocales.length : 0 (si no, null.length tronarí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:

zsh · ids-activos.ts
$ tsc ids-activos.ts
$ node ids-activos.js
[ 1, 3 ]

💡 tareas tiene una tercera propiedad (titulo) que la restricción ni pide — de más no molesta, solo exige el mínimo (id y activo).

❓ 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 propiedad length de tipo number. 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 .length de forma nativa, y 42 (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, y typeof no los distingue. Para eso existe Array.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 .length por 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). obtenerLongitud solo acepta valores que tengan .length — por eso uno funciona con 42 y el otro no.

7. ¿Qué es declaration merging (visto en la sección bonus)?

La técnica de declarar una interface con 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 .prototype a 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 T a una interfaz con una sola propiedad obligatoria (peso o id). 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 tipo number) no tiene la forma que T exige (un objeto con propiedad length: number). No es que number esté "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).