Callbacks y funciones flecha en JavaScript
En esta lección
- Las funciones son valores
- Callbacks: funciones que se pasan a otras
- Funciones que devuelven funciones
- Funciones de orden superior
- Funciones flecha a fondo
- Parámetros por defecto y rest (recordatorio)
- El objeto arguments y por qué evitarlo
- IIFE: ejecutar una función al momento
- Funciones puras y efectos secundarios
- Recursividad básica
- Errores frecuentes
- Resumen
En la lección de funciones aprendiste a declararlas, a pasarles parámetros y a devolver resultados con return. Aquí vas un paso más allá: vas a tratar las funciones como datos que se guardan, se pasan y se devuelven. Es la base de casi todo el JavaScript moderno: los eventos, los métodos de arrays como map y filter, las promesas o los frameworks como React. Cuando lo entiendas, mucho código que ahora te parece mágico empezará a tener sentido.
Las funciones son valores
En JavaScript una función es un valor, igual que un número o un texto. Eso significa que puedes hacer con ella lo mismo que con cualquier otro valor:
// Guardarla en una variable
const saludar = function (nombre) {
return `Hola, ${nombre}`;
};
// Meterla en un array
const acciones = [saludar, (n) => n.toUpperCase()];
console.log(acciones[1]("ana")); // ANA
// Guardarla como propiedad de un objeto
const calculadora = {
sumar: (a, b) => a + b,
restar: (a, b) => a - b,
};
console.log(calculadora.restar(10, 4)); // 6
La primera forma, const saludar = function (...) {...}, se llama expresión de función: una función sin nombre guardada en una variable. Fíjate en la diferencia clave que ya viste:
saludares la función (el valor).saludar("Eva")es el resultado de llamarla.
Cuando un lenguaje permite usar las funciones como cualquier otro valor, se dice que tiene funciones de primera clase. De esta idea salen los callbacks y las funciones de orden superior.
Callbacks: funciones que se pasan a otras
Un callback es una función que le pasas a otra para que ella decida cuándo llamarla. Tú entregas el “qué hacer” y la otra función se encarga del “cuándo” o del “a quién”.
function repetir(veces, accion) {
for (let i = 1; i <= veces; i++) {
accion(i);
}
}
repetir(3, (n) => console.log(`Vuelta ${n}`));
// Vuelta 1
// Vuelta 2
// Vuelta 3
repetir no sabe qué vas a hacer en cada vuelta. Solo sabe repetir. Eso la hace reutilizable: con otro callback hace otra cosa distinta.
Un ejemplo más realista, en una tienda online:
function aplicarDescuento(precios, calcular) {
const resultado = [];
for (const precio of precios) {
resultado.push(calcular(precio));
}
return resultado;
}
const rebajas = (p) => p * 0.8;
const blackFriday = (p) => p * 0.5;
aplicarDescuento([100, 50, 20], rebajas); // [80, 40, 16]
aplicarDescuento([100, 50, 20], blackFriday); // [50, 25, 10]
Ya usas callbacks a diario, aunque no los llamaras así:
| Código | ¿Quién llama al callback? | ¿Cuándo? |
|---|---|---|
boton.addEventListener("click", fn) | El navegador | Cuando alguien hace clic |
setTimeout(fn, 1000) | El navegador | Pasado un segundo |
notas.map(fn) | El método map | Una vez por cada elemento |
promesa.then(fn) | La promesa | Cuando llega el resultado |
Los dos primeros son callbacks asíncronos (se ejecutan más tarde) y los dos últimos… map es síncrono (se ejecuta al momento) y then es asíncrono. La diferencia la verás en la lección de asincronía.
Cuidado: pasa la función sin paréntesis.
addEventListener("click", guardar())ejecutaguardaren ese mismo momento y pasa su resultado (normalmenteundefined), no la función.
Funciones que devuelven funciones
Una función también puede fabricar y devolver otra función:
function crearMultiplicador(factor) {
return (n) => n * factor;
}
const doble = crearMultiplicador(2);
const triple = crearMultiplicador(3);
console.log(doble(5), triple(5)); // 10 15
Cada función devuelta recuerda el factor con el que se creó, aunque crearMultiplicador ya haya terminado. Ese mecanismo se llama closure y lo tienes explicado en la lección de ámbito y closures.
Sirve para crear funciones configurables sin repetir código:
function crearValidador(minimo) {
return (texto) => texto.length >= minimo;
}
const esContrasenaValida = crearValidador(8);
esContrasenaValida("1234"); // false
esContrasenaValida("superSegura"); // true
Funciones de orden superior
Una función de orden superior es la que recibe una función como argumento, devuelve una función, o las dos cosas. repetir, aplicarDescuento y crearMultiplicador lo son.
Los métodos de arrays más útiles son funciones de orden superior. Para que veas que no hay magia, así podrías escribir tu propio filter:
function miFiltro(lista, condicion) {
const resultado = [];
for (const elemento of lista) {
if (condicion(elemento)) {
resultado.push(elemento);
}
}
return resultado;
}
miFiltro([3, 8, 1, 9], (n) => n > 5); // [8, 9]
Es exactamente lo que hace [3, 8, 1, 9].filter((n) => n > 5). La ventaja es que el bucle se escribe una sola vez y en cada uso solo cambias la condición. Los tienes todos en map, filter, reduce y otros métodos.
Funciones flecha a fondo
Las funciones flecha (=>) son la forma más usada de escribir callbacks. Estos son los detalles que conviene dominar.
Paréntesis y retorno implícito
const cuadrado = n => n * n; // un parámetro: paréntesis opcionales
const cuadrado2 = (n) => n * n; // con paréntesis (recomendado, más uniforme)
const sumar = (a, b) => a + b; // dos o más: obligatorios
const hola = () => "Hola"; // ninguno: obligatorios
Si después de la flecha hay una expresión, se devuelve sola (retorno implícito). Si hay llaves, es un bloque normal y necesitas return:
const conIva = (p) => p * 1.21; // devuelve el resultado
const conIva2 = (p) => { p * 1.21; }; // devuelve undefined: falta return
const conIva3 = (p) => { return p * 1.21; }; // correcto
Devolver un objeto: entre paréntesis
Aquí cae todo el mundo alguna vez. Las llaves de un objeto se confunden con las llaves de un bloque:
const mal = (nombre) => { nombre: nombre };
console.log(mal("Taza")); // undefined
const bien = (nombre, precio) => ({ nombre, precio });
console.log(bien("Taza", 8)); // { nombre: 'Taza', precio: 8 }
En mal, JavaScript entiende { ... } como un bloque de código y nombre: como una etiqueta, así que no devuelve nada. Los paréntesis le dicen que es una expresión: un objeto.
Las flechas no tienen this propio
Las funciones normales reciben su propio this según cómo se las llame. Las flechas no: usan el this del sitio donde se escribieron. Eso es justo lo que quieres dentro de un método:
const gimnasio = {
nombre: "FitZone",
socios: ["Ana", "Luis"],
listar() {
this.socios.forEach((socio) => {
console.log(`${socio} es socio de ${this.nombre}`);
});
},
};
gimnasio.listar();
// Ana es socio de FitZone
// Luis es socio de FitZone
La flecha de dentro usa el this de listar, que es gimnasio. Pero por la misma razón, no uses una flecha como método de un objeto:
const contador = {
valor: 0,
sumar: () => {
this.valor++; // this NO es contador
},
};
Resumen práctico: métodos con la sintaxis corta sumar() { ... }; callbacks con flechas. Las flechas tampoco se pueden usar con new ni tienen arguments.
Parámetros por defecto y rest (recordatorio)
Ya los viste en funciones. Un par de detalles más:
function crearUsuario(nombre, rol = "alumno", activo = true) {
return `${nombre} (${rol}, ${activo ? "activo" : "inactivo"})`;
}
crearUsuario("Eva"); // "Eva (alumno, activo)"
crearUsuario("Eva", undefined, false); // "Eva (alumno, inactivo)"
El valor por defecto se usa cuando el argumento es undefined, tanto si falta como si lo pasas así a propósito.
El parámetro rest recoge “el resto” de argumentos en un array de verdad. Tiene que ir el último:
function registrar(nivel, ...mensajes) {
console.log(`[${nivel}]`, mensajes.join(" "));
}
registrar("INFO", "Servidor", "arrancado", "en", "3000");
// [INFO] Servidor arrancado en 3000
El objeto arguments y por qué evitarlo
En código antiguo verás esto:
function sumarViejo() {
let total = 0;
for (let i = 0; i < arguments.length; i++) {
total += arguments[i];
}
return total;
}
sumarViejo(1, 2, 3); // 6
Dentro de toda función normal existe arguments, con todos los argumentos recibidos. Tiene tres problemas:
- No es un array: se parece, pero no tiene
map,filternireduce. - No se ve en la firma: leyendo
function sumarViejo()nadie adivina que acepta argumentos. - No existe en las funciones flecha: dentro de una flecha,
argumentses el de la función normal que la rodea, lo que provoca errores muy confusos.
Usa siempre parámetros rest: function sumarTodos(...numeros).
IIFE: ejecutar una función al momento
Una IIFE (Immediately Invoked Function Expression) es una función que se define y se ejecuta en el mismo instante:
const config = (() => {
const entorno = "produccion"; // variable privada
return { entorno, debug: false };
})();
console.log(config.entorno); // produccion
Los primeros paréntesis convierten la función en una expresión y los últimos () la llaman. Antes de los módulos se usaban mucho para no llenar el ámbito global. Hoy casi no hacen falta, pero las encontrarás en código antiguo. Tienes más detalles en ámbito y closures.
Funciones puras y efectos secundarios
Una función pura:
- Con los mismos argumentos, siempre devuelve lo mismo.
- No cambia nada de fuera: ni variables globales, ni sus parámetros, ni la página.
// Pura: solo depende de su entrada
function precioConIva(precio) {
return precio * 1.21;
}
// Impura: modifica una variable de fuera
let total = 0;
function sumarAlTotal(precio) {
total += precio;
}
Cualquier cosa que una función haga además de devolver un valor es un efecto secundario: cambiar una variable externa, modificar un array recibido, escribir en la consola, tocar el DOM, guardar en localStorage o hacer un fetch.
Los efectos secundarios no son malos: sin ellos un programa no haría nada visible. La idea es separarlos. Calcula con funciones puras y deja los efectos en pocas funciones concretas. Las funciones puras son fáciles de probar y de entender, porque no esconden sorpresas.
Un caso típico es modificar un array recibido:
const carrito = ["libro"];
function anadirPuro(lista, producto) {
return [...lista, producto]; // crea un array nuevo
}
const nuevo = anadirPuro(carrito, "taza");
console.log(carrito, nuevo); // [ 'libro' ] [ 'libro', 'taza' ]
El original no cambia. Los ... (spread) los tienes en desestructuración y spread.
Recursividad básica
Una función recursiva es la que se llama a sí misma. Toda función recursiva necesita dos partes:
- Caso base: cuándo parar.
- Caso recursivo: la llamada a sí misma con un problema más pequeño.
function cuentaAtras(n) {
if (n === 0) { // caso base
console.log("¡Despegue!");
return;
}
console.log(n);
cuentaAtras(n - 1); // caso recursivo
}
cuentaAtras(3); // 3, 2, 1, ¡Despegue!
El ejemplo clásico es el factorial (5! = 5 · 4 · 3 · 2 · 1):
function factorial(n) {
if (n <= 1) return 1;
return n * factorial(n - 1);
}
factorial(5); // 120
Para una cuenta atrás, un bucle es más sencillo. La recursividad brilla con estructuras anidadas, donde no sabes cuántos niveles hay: carpetas dentro de carpetas, comentarios con respuestas, menús con submenús.
const carpeta = {
nombre: "proyecto",
archivos: ["index.html"],
subcarpetas: [
{ nombre: "css", archivos: ["estilos.css", "reset.css"], subcarpetas: [] },
{ nombre: "js", archivos: ["app.js"], subcarpetas: [
{ nombre: "utils", archivos: ["fechas.js"], subcarpetas: [] },
] },
],
};
function contarArchivos(c) {
let total = c.archivos.length;
for (const sub of c.subcarpetas) {
total += contarArchivos(sub);
}
return total;
}
contarArchivos(carpeta); // 5
Cuidado: si olvidas el caso base, la función se llama sin fin hasta que JavaScript se queda sin memoria para las llamadas:
RangeError: Maximum call stack size exceeded.
Errores frecuentes
- Llamar en vez de pasar:
setTimeout(avisar(), 1000)ejecutaavisarya. Debe sersetTimeout(avisar, 1000). - Olvidar el
returncon llaves:(n) => { n * 2 }devuelveundefined. - Devolver un objeto sin paréntesis:
() => { id: 1 }devuelveundefined; escribe() => ({ id: 1 }). - Flecha como método:
thisno será el objeto. - Recursividad sin caso base: error de pila desbordada.
- Usar
argumentsen lugar de parámetros rest.
Resumen
| Concepto | Qué es | Ejemplo |
|---|---|---|
| Función como valor | Se guarda, se pasa y se devuelve | const f = saludar; |
| Callback | Función que se pasa a otra para que la llame | boton.addEventListener("click", fn) |
| Orden superior | Recibe o devuelve funciones | map, crearMultiplicador |
| Retorno implícito | Flecha sin llaves devuelve la expresión | (n) => n * 2 |
| Objeto desde flecha | Entre paréntesis | () => ({ id: 1 }) |
this en flechas | Usa el de fuera | Callbacks dentro de métodos |
| Parámetro rest | Resto de argumentos en un array | (...numeros) |
| IIFE | Función que se ejecuta al crearla | (() => { ... })() |
| Función pura | Misma entrada, misma salida, sin efectos | (p) => p * 1.21 |
| Recursividad | Función que se llama a sí misma | factorial(n - 1) |
Pruébalo tú
Cambia el código y pulsa Ejecutar (o Ctrl + Enter).
Pon a prueba lo que has aprendido
¿Te ha quedado claro? Márcala y verás tu progreso en el explorador.