En Java, un método pertenece a una clase y sólo existe dentro de ella. En JavaScript, una función es un valor, igual que un número o un texto: se guarda en variables, se pasa a otras funciones y se devuelve como resultado. Casi todo lo que harás en el navegador (eventos, peticiones, métodos de array) se apoya en esa idea.
1 // Tres formas de escribir una función
La misma función se puede escribir de tres maneras:
// 1. Declaración de función
function sumar(a, b) {
return a + b;
}
// 2. Expresión de función: una función sin nombre guardada en una constante
const restar = function (a, b) {
return a - b;
};
// 3. Función flecha
const multiplicar = (a, b) => a * b;Una función flecha (arrow function) es una forma abreviada de escribir una expresión de función. Toma su nombre del símbolo =>, que separa las dos mitades: a la izquierda, los parámetros que recibe; a la derecha, el cuerpo, lo que hace o devuelve. No lleva la palabra function ni un nombre propio, así que para usarla se guarda en una constante o se pasa directamente como argumento a otra función. Son las más habituales en código moderno, sobre todo para funciones cortas.
La mejor forma de entenderlas es convertir una función normal paso a paso. Cada bloque es una versión alternativa de la misma función, no un programa seguido:
// 1. Punto de partida: una declaración de función
function doble(n) {
return n * 2;
}
// 2. Se guarda en una constante como expresión de función
const doble = function (n) {
return n * 2;
};
// 3. Se quita la palabra function y se escribe => entre parámetros y cuerpo
const doble = (n) => {
return n * 2;
};
// 4. El cuerpo es una sola expresión: fuera las llaves y el return
const doble = (n) => n * 2;
// 5. Hay un único parámetro: los paréntesis se pueden quitar
const doble = n => n * 2;Donde más se nota el ahorro es al pasar una función como argumento, que es lo que harás a diario. setTimeout recibe la función que debe ejecutar pasado un tiempo:
// Con una expresión de función
setTimeout(function () {
console.log('Han pasado dos segundos');
}, 2000);
// Con una función flecha: sin parámetros, paréntesis vacíos
setTimeout(() => console.log('Han pasado dos segundos'), 2000);La sintaxis tiene algunas abreviaturas más, que conviene reconocer al leer código:
| Caso | Forma |
|---|---|
| Un único parámetro | n => n * 2 (los paréntesis son opcionales) |
| Ningún parámetro | () => console.log('hola') |
| Cuerpo de una expresión | (a, b) => a + b (el return es implícito) |
| Cuerpo con varias líneas | (a, b) => { const s = a + b; return s; } |
| Devolver un objeto | n => ({ valor: n }) (el objeto va entre paréntesis) |
Hay dos diferencias reales entre la declaración y las otras dos formas. La primera es el hoisting: una declaración de función se puede llamar antes de la línea en la que aparece, porque el motor la registra al entrar en el ámbito. La segunda afecta a las flechas, que no tienen su propio this; tiene mucha importancia al trabajar con objetos y la verás en la unidad de clases.
Si escribes una flecha con llaves, el return deja de ser implícito. n => { n * 2 } devuelve undefined, porque las llaves abren un bloque de código, no un valor. Es uno de los errores más repetidos con map y filter.
2 // Parámetros sin contrato
JavaScript no verifica cuántos argumentos recibe una función ni de qué tipo son. Si faltan, valen undefined; si sobran, se ignoran. Por eso existen dos herramientas para dejar claro qué espera una función:
// Parámetro por defecto: se usa si el argumento falta o es undefined
function saludar(nombre = 'invitado') {
return `Hola, ${nombre}`;
}
saludar(); // 'Hola, invitado'
saludar('Marta'); // 'Hola, Marta'
// Parámetro rest: agrupa en un array todos los argumentos restantes
function media(...notas) {
let suma = 0;
for (const nota of notas) {
suma += nota;
}
return suma / notas.length;
}
media(7, 8, 9); // 8Esto tiene una consecuencia que choca mucho viniendo de Java: como el número de parámetros no forma parte de la función, no hay forma de tener dos versiones de una misma función que se distingan por lo que reciben.
function area(lado) {
return lado * lado;
}
function area(base, altura) { // no es otra versión: sustituye a la de arriba
return base * altura;
}
area(4); // NaN: se ejecuta la segunda, con altura = undefined
area(4, 3); // 12En JavaScript no hay sobrecarga de métodos como en Java. Si defines dos funciones con el mismo nombre, la segunda sustituye a la primera, sin error ni aviso. Y por el hoisting, incluso las llamadas escritas antes de la segunda declaración usan ya la segunda.
Cuando una función debe comportarse distinto según lo que reciba, se escribe una sola función que lo decide dentro, con parámetros por defecto o comprobando los valores:
function area(base, altura = base) { // si falta altura, es un cuadrado
return base * altura;
}
area(4); // 16
area(4, 3); // 123 // Pasar funciones como argumento
Como una función es un valor, puedes pasarla a otra función. La función que se pasa se llama callback, y la que la recibe, función de orden superior. Es la manera de decir «haz esto, y cuando llegue el momento, llama a esto otro»:
function aplicar(lista, fn) {
const resultado = [];
for (const elemento of lista) {
resultado.push(fn(elemento));
}
return resultado;
}
const doble = n => n * 2;
aplicar([1, 2, 3], doble); // [2, 4, 6]
aplicar([1, 2, 3], n => n + 10); // [11, 12, 13]aplicar no sabe qué transformación se va a hacer; sólo sabe recorrer una lista. Quien la llama decide el comportamiento pasando otra función. Este patrón está en todas partes: addEventListener recibe la función que se ejecutará al hacer clic, setTimeout la que se ejecutará pasado un tiempo y map la que transforma cada elemento.
Escribir el nombre de una función sin paréntesis es hablar de la función; con paréntesis, es ejecutarla. Al pasar un callback casi siempre quieres lo primero.
4 // Funciones que devuelven funciones
El paso contrario también es posible: una función puede fabricar y devolver otra función.
function crearMultiplicador(factor) {
return numero => numero * factor;
}
const triple = crearMultiplicador(3);
const porCien = crearMultiplicador(100);
triple(5); // 15
porCien(0.21); // 21Fíjate en algo que no es evidente: cuando llamas a triple(5), crearMultiplicador ya terminó hace rato, y aun así la función devuelta sigue sabiendo que factor vale 3. Eso es un closure, y es el tema de la unidad siguiente.
Quieres formatear precios de distintas formas: con euros, con dos decimales y con IVA incluido, sin repetir la lógica de recorrer la lista.
- Separas el qué del cómo: una función
formatear(precios, fn)recorre la lista y aplicafna cada precio. - Defines cada formato como una función pequeña:
p => p.toFixed(2) + ' €'yp => (p * 1.21).toFixed(2) + ' €'. - Llamas a la misma función con distintos callbacks:
formatear([10, 4.5], p => p.toFixed(2) + ' €')da['10.00 €', '4.50 €']. - Fabricas variantes con una función que devuelve funciones:
const conImpuesto = tipo => p => (p * (1 + tipo)).toFixed(2) + ' €';y despuésformatear(precios, conImpuesto(0.21)).
5 // Para practicar
Los primeros ejercicios practican la sintaxis de funciones; los dos últimos, pasar funciones como argumento. Cada ejercicio trae el enunciado en los comentarios del código.
Valor absolutotraining-daw-sv/modulo-00/03-funciones-como-valores/01_absPares anterior y siguientetraining-daw-sv/modulo-00/03-funciones-como-valores/02_next_pairsNúmeros parestraining-daw-sv/modulo-00/03-funciones-como-valores/03_even_numbersPing pongtraining-daw-sv/modulo-00/03-funciones-como-valores/04_ping_pongTablas de multiplicartraining-daw-sv/modulo-00/03-funciones-como-valores/05_multiplicationsParámetros por defecto y resttraining-daw-sv/modulo-00/03-funciones-como-valores/06_advanced_parametersUn filtro que recibe una condicióntraining-daw-sv/modulo-00/03-funciones-como-valores/07_custom_filterEl mismo filtro con funciones flechatraining-daw-sv/modulo-00/03-funciones-como-valores/08_arrow_custom_filterAutoevaluación
¿Qué diferencia hay entre boton.addEventListener('click', saludar) y boton.addEventListener('click', saludar())?
saludar es el valor función, que el navegador llamará al hacer clic. Con paréntesis, saludar() se ejecuta en ese mismo instante y lo que se registra es su resultado, normalmente undefined.Puedes llamar a una función declarada con function sumar() {} antes de la línea en la que aparece, pero no a const sumar = () => {}. ¿Por qué?
sumar al entrar en el ámbito, así que existe desde la primera línea. Con const, la variable existe pero no tiene valor hasta que se ejecuta su línea, y usarla antes lanza un error.Llamas a sumar(2) con una función function sumar(a, b) { return a + b; }. En Java no compilaría; en JavaScript devuelve NaN. Explica el recorrido.
b, vale undefined, y 2 + undefined convierte undefined a número, que da NaN. Un parámetro por defecto, b = 0, evitaría el problema.