Lección 4 de 8. Las funciones son la frontera del código: lo que entra y lo que sale. Anotar esa frontera es lo que más rinde de TypeScript, porque el compilador comprueba cada llamada, en cualquier archivo, contra lo que la función dice que acepta. Al final, la primera función genérica.
Anotar parámetros y valor devuelto
En una función se anotan los parámetros, siempre, y el valor devuelto cuando ayuda a leerla. Todo lo demás (declaraciones, funciones flecha, parámetros por omisión, resto) funciona como en JavaScript, y lo tienes en la lección de funciones del curso de JavaScript.
function precioFinal(precio: number, iva: number): number {
return precio * (1 + iva);
}
const redondear = (n: number): number => Math.round(n * 100) / 100;
console.log(redondear(precioFinal(19.99, 0.21)));Ábrelo en el editor y cámbialo →
salida24.19
Si no anotas un parámetro, el modo estricto no te deja seguir: un parámetro sin tipo sería any, y any apaga la revisión de todo lo que lo usa.
function doble(x) {
return x * 2;
}Ábrelo en el editor y cámbialo →
tsc --strictimplicito.ts(1,16): error TS7006: El parámetro 'x' tiene un tipo 'any' implícitamente.
El valor devuelto, en cambio, se puede dejar sin anotar: TypeScript lo deduce del return. Anotarlo tiene una ventaja: si dentro de la función te equivocas y devuelves otra cosa, el error sale en la función y no en el sitio, lejos, donde se usa el resultado.
El error que te ahorra: llamar a una función con argumentos de menos
function precioFinal(precio, iva) {
return precio * (1 + iva);
}
console.log("Total:", precioFinal(100));node argumentos_antes.mjsTotal: NaN
JavaScript no comprueba cuántos argumentos recibe una función: los que faltan valen undefined, 1 + undefined es NaN, y el total sale como «NaN» sin ningún error. En TypeScript:
function precioFinal(precio: number, iva: number): number {
return precio * (1 + iva);
}
console.log("Total:", precioFinal(100));tsc --strictargumentos.ts(4,23): error TS2554: Se esperaban 2 argumentos, pero se obtuvieron 1.
Opcionales, por omisión y resto
Si un parámetro puede faltar, se dice con ? o dándole un valor por omisión. Con ? su tipo incluye undefined y hay que comprobarlo; con un valor por omisión no hace falta, porque nunca llega vacío. Los parámetros de resto se anotan como un array:
function saludar(nombre: string, titulo?: string): string {
return titulo ? `Hola, ${titulo} ${nombre}` : `Hola, ${nombre}`;
}
function precioFinal(precio: number, iva = 0.21): number { // iva es number
return Math.round(precio * (1 + iva) * 100) / 100;
}
function sumar(...numeros: number[]): number {
return numeros.reduce((a, b) => a + b, 0);
}
console.log(saludar("Ruiz"), "|", saludar("Ruiz", "Dra."));
console.log(precioFinal(100), precioFinal(100, 0.04));
console.log(sumar(), sumar(1, 2, 3));Ábrelo en el editor y cámbialo →
salidaHola, Ruiz | Hola, Dra. Ruiz 121 104 0 6
Funciones como valores: tipos de función y callbacks
En JavaScript las funciones se pasan como cualquier otro valor. Su tipo se escribe con la forma de una flecha: (a: number, b: number) => number es «una función que recibe dos números y devuelve un número». Los nombres de los parámetros del tipo son solo documentación:
type Operacion = (a: number, b: number) => number;
const sumar: Operacion = (a, b) => a + b; // a y b ya son number
const multiplicar: Operacion = (x, y) => x * y;
function aplicar(op: Operacion, a: number, b: number): number {
return op(a, b);
}
console.log(aplicar(sumar, 6, 7), aplicar(multiplicar, 6, 7));
const precios = [10, 25.5, 8];
const conIva = precios.map((p) => p * 1.21); // p es number: lo deduce de precios
console.log(conIva.map((p) => p.toFixed(2)));Ábrelo en el editor y cámbialo →
salida13 42 [ '12.10', '30.86', '9.68' ]
En sumar y en los map no hay ninguna anotación dentro de la flecha, y sin embargo a, b y p son números. Es el tipado contextual: cuando una función se escribe en un sitio que ya espera un tipo de función concreto, TypeScript deduce los parámetros de ahí. Por eso en los callbacks casi nunca hace falta anotar nada.
void: funciones que no devuelven nada
Una función que no devuelve ningún valor tiene tipo de retorno void. Sirve, sobre todo, para que nadie use por error su resultado, que en JavaScript sería undefined:
function registrar(mensaje: string): void {
console.log(`[registro] ${mensaje}`);
}
const r = registrar("inicio");
console.log(r.length); // errorÁbrelo en el editor y cámbialo →
tsc --strictvoid.ts(6,15): error TS2339: La propiedad 'length' no existe en el tipo 'void'.
() => void, puedes pasarle una función que sí devuelve algo, y el compilador no protesta. Es a propósito, para que lista.forEach((x) => otra.push(x)) funcione aunque push devuelva un número. Lo explicamos en las rarezas de TypeScript.Sobrecargas: varias firmas para una función
A veces una función acepta combinaciones de argumentos distintas, y el tipo que devuelve depende de cuál uses. Las sobrecargas lo describen: se escriben varias firmas sin cuerpo y, debajo, una sola implementación que las cubre todas. Desde fuera solo se ven las firmas:
function crearFecha(marcaDeTiempo: number): Date;
function crearFecha(anio: number, mes: number, dia: number): Date;
function crearFecha(a: number, mes?: number, dia?: number): Date {
if (mes !== undefined && dia !== undefined) {
return new Date(Date.UTC(a, mes - 1, dia));
}
return new Date(a);
}
console.log(crearFecha(0).toISOString());
console.log(crearFecha(2026, 10, 20).toISOString());Ábrelo en el editor y cámbialo →
salida1970-01-01T00:00:00.000Z 2026-10-20T00:00:00.000Z
function crearFecha(marcaDeTiempo: number): Date;
function crearFecha(anio: number, mes: number, dia: number): Date;
function crearFecha(a: number, mes?: number, dia?: number): Date {
if (mes !== undefined && dia !== undefined) {
return new Date(Date.UTC(a, mes - 1, dia));
}
return new Date(a);
}
crearFecha(2026, 10); // ¿año y mes, sin día?Ábrelo en el editor y cámbialo →
tsc --strictsobrecargas_mal.ts(10,1): error TS2575: Ninguna sobrecarga espera argumentos 2, pero existen sobrecargas que esperan argumentos 1 o 3.
La implementación admitiría dos argumentos, pero como no hay ninguna firma con dos, la llamada se rechaza. Úsalas con moderación: casi siempre basta con una unión o con parámetros opcionales, que son más fáciles de leer.
El parámetro this
En JavaScript, el valor de this depende de cómo se llama la función, no de dónde se escribió; las reglas están en la lección de clases y this del curso de JavaScript. TypeScript permite declarar qué this espera una función con un primer parámetro llamado this, que no es un argumento real y desaparece al compilar:
type Cuenta = { titular: string; saldo: number };
function describir(this: Cuenta): string {
return `${this.titular}: ${this.saldo} EUR`;
}
const cuenta = { titular: "Ana", saldo: 120, describir };
console.log(cuenta.describir()); // this es cuenta: bien
const suelta = cuenta.describir;
console.log(suelta()); // error: aquí this no es una CuentaÁbrelo en el editor y cámbialo →
tsc --strictthis.ts(11,13): error TS2684: El contexto 'this' de tipo 'void' no se puede asignar al contexto 'this' de tipo 'Cuenta' del método.
La primera función genérica
Imagina una función que devuelve el primer elemento de un array. ¿De qué tipo es su parámetro? Si escribes any[], sirve para cualquier array, pero pierdes la información: el resultado también es any, y el compilador deja pasar cualquier cosa.
function primero(lista: any[]): any {
return lista[0];
}
const n = primero([3, 1, 2]);
console.log(n.toUpperCase()); // tsc no dice nada: n es anyÁbrelo en el editor y cámbialo →
node primero_any.tsTypeError: n.toUpperCase is not a function
La solución es un parámetro de tipo: un nombre entre < y >, por costumbre T, que funciona como un hueco que se rellena en cada llamada con el tipo que corresponda:
function primero<T>(lista: T[]): T | undefined {
return lista[0];
}
const pan = primero(["pan", "té"]); // string | undefined
const tres = primero([3, 1, 2]); // number | undefined
console.log(pan?.toUpperCase(), tres?.toFixed(1));
console.log(tres?.toUpperCase()); // error: tres es un númeroÁbrelo en el editor y cámbialo →
tsc --strictprimero.ts(8,19): error TS2339: La propiedad 'toUpperCase' no existe en el tipo 'number'.
Fíjate en que devuelve T | undefined y no T: un array vacío no tiene primer elemento. Los genéricos tienen su propia lección, la 6, con restricciones y varios parámetros de tipo; con esta idea ya puedes leer la mayoría de las firmas de las bibliotecas.
Resumen
| Para | Escribe | Recuerda |
|---|---|---|
| Anotar una función | (precio: number): number | los parámetros siempre; el retorno, si ayuda |
| Un parámetro que puede faltar | titulo?: string o iva = 0.21 | con ? hay que comprobarlo |
| Muchos argumentos | ...numeros: number[] | — |
| El tipo de una función | (a: number) => string | en callbacks, se deduce solo |
| No devuelve nada | : void | los callbacks void sí pueden devolver algo |
| Combinaciones de argumentos | sobrecargas | mejor una unión si basta |
| Fijar el this | function f(this: Tipo) | no es un argumento real |
| Que sirva para cualquier tipo | function f<T>(x: T[]): T | any pierde la información |
Retos
Reto 1. Escribe una función repetir que reciba un texto y, opcionalmente, cuántas veces repetirlo (2 por omisión) y un separador (un espacio por omisión), y que devuelva el resultado.
Ver la solución
function repetir(texto: string, veces = 2, separador = " "): string {
return Array(veces).fill(texto).join(separador);
}
console.log(repetir("eco"));
console.log(repetir("na", 4, "-"));Ábrelo en el editor y cámbialo →
salidaeco eco na-na-na-na
Reto 2. Define el tipo Filtro para funciones que reciben un número y devuelven si pasa o no, y escribe aplicarFiltros(lista: number[], ...filtros: Filtro[]), que devuelva los números que pasan todos los filtros.
Ver la solución
type Filtro = (n: number) => boolean;
function aplicarFiltros(lista: number[], ...filtros: Filtro[]): number[] {
return lista.filter((n) => filtros.every((f) => f(n)));
}
const esPar: Filtro = (n) => n % 2 === 0;
const mayorQue10: Filtro = (n) => n > 10;
console.log(aplicarFiltros([4, 12, 15, 18, 7], esPar, mayorQue10));Ábrelo en el editor y cámbialo →
salida[ 12, 18 ]
Reto 3. Escribe una función genérica ultimo que devuelva el último elemento de un array, con el tipo correcto. Comprueba que ultimo([true, false]) es boolean | undefined intentando asignarlo a una variable de tipo string.
Ver la solución
function ultimo<T>(lista: T[]): T | undefined {
return lista[lista.length - 1];
}
const b = ultimo([true, false]);
const s: string = b; // errorÁbrelo en el editor y cámbialo →
tsc --strictreto3.ts(6,7): error TS2322: El tipo 'boolean | undefined' no se puede asignar al tipo 'string'. El tipo 'undefined' no se puede asignar al tipo 'string'.
También valdría lista.at(-1), que ya devuelve T | undefined.
Todo el código de esta lección se comprobó con tsc 7.0.2 en modo estricto (--strict --locale es) y se ejecutó con Node.js 24.22 en un contenedor limpio antes de publicarla; los mensajes de error y las salidas están copiados de esa ejecución. ¿Quieres probar algo? Usa el editor de TypeScript.
