Saltar al contenido
schedule 12 min JavaScript

Funciones

Imagina que cada vez que quieres calcular algo, tienes que escribir las mismas 10 líneas de código. Y luego otra vez. Y otra. Las funciones existen para que escribas esa lógica una vez y la reutilices donde quieras. Son la base de cualquier programa organizado, y sin ellas tu código sería un bloque interminable de instrucciones repetidas. Ya definiste funciones con pseudocódigo en la sección de Lógica — ahora vas a ver la sintaxis real de JavaScript y las posibilidades extra que ofrece.

Declaración de función (function declaration)

La forma clásica de crear una función. Se puede llamar antes de declararla gracias al hoisting (JavaScript la "sube" al principio del scope):

app.js
// Puedes llamarla antes de declararla — hoisting
saludar("María"); // "¡Hola, María!"

function saludar(nombre) {
    console.log(`¡Hola, ${nombre}!`);
}

Expresión de función (function expression)

Aquí guardas una función anónima en una variable. No tiene hoisting — si la llamas antes de declararla, obtienes un error:

app.js
// Esto daría error: calcularPropina is not defined
// calcularPropina(50);

const calcularPropina = function(cuenta) {
    return cuenta * 0.15;
};

console.log(calcularPropina(50)); // 7.5

Arrow functions: la forma moderna

Las arrow functions (=>) son la sintaxis que más vas a usar. Son más compactas y tienen un comportamiento especial con this (eso lo verás más adelante). Para la mayoría de los casos, son tu opción por defecto:

app.js
// Arrow function completa
const calcularPropina = (cuenta) => {
    return cuenta * 0.15;
};

// Con un solo parámetro, los paréntesis son opcionales
const duplicar = numero => {
    return numero * 2;
};

// Return implícito: si el cuerpo es UNA sola expresión,
// puedes quitar las llaves y el return
const duplicarCorto = numero => numero * 2;

// Sin parámetros: paréntesis obligatorios
const saludar = () => "¡Hola!";

console.log(duplicarCorto(5));  // 10
console.log(saludar());         // "¡Hola!"

Regla práctica: usa arrow functions por defecto. Usa function clásica solo cuando necesites hoisting o cuando trabajes con métodos de objetos que necesiten su propio this.

Parámetros y valores por defecto

Puedes definir valores por defecto para los parámetros. Si no pasas un argumento, se usa el valor por defecto en lugar de undefined:

app.js
const crearUsuario = (nombre, rol = "visitante", activo = true) => {
    return { nombre, rol, activo };
};

console.log(crearUsuario("Ana"));
// { nombre: "Ana", rol: "visitante", activo: true }

console.log(crearUsuario("Carlos", "admin"));
// { nombre: "Carlos", rol: "admin", activo: true }

console.log(crearUsuario("Bot", "moderador", false));
// { nombre: "Bot", rol: "moderador", activo: false }

Return: devolver valores

Una función sin return devuelve undefined. Usa return para sacar un resultado de la función:

app.js
// Función que DEVUELVE un valor — puedes usar el resultado
const calcularIVA = (precio, porcentaje = 21) => {
    const iva = precio * (porcentaje / 100);
    return precio + iva;
};

const precioFinal = calcularIVA(100);
console.log(`Total con IVA: ${precioFinal}€`); // "Total con IVA: 121€"

// Return implícito en arrow functions (sin llaves)
const calcularIVACorto = (precio, porcentaje = 21) =>
    precio * (1 + porcentaje / 100);

// Función que NO devuelve nada — solo ejecuta una acción
const mostrarAlerta = (mensaje) => {
    console.log(`⚠️ ${mensaje}`);
    // No hay return — devuelve undefined
};

Ejemplo práctico: calculadora de pizza party

Tu jefe te dice: "organiza la pizza party del equipo". Necesitas saber cuántas pizzas pedir. Vamos a hacer una función para eso:

pizza-party.js
const calcularPizzas = (personas, porciones = 3, porcionesPorPizza = 8) => {
    const totalPorciones = personas * porciones;
    const pizzas = Math.ceil(totalPorciones / porcionesPorPizza);
    return pizzas;
};

// 10 personas, 3 porciones cada una, pizzas de 8 porciones
console.log(calcularPizzas(10)); // 4

// 15 personas, 2 porciones cada una
console.log(calcularPizzas(15, 2)); // 4

// Ahora con el resumen completo
const resumenPizzaParty = (personas, porciones = 3) => {
    const pizzas = calcularPizzas(personas, porciones);
    const precioEstimado = pizzas * 12; // 12€ por pizza

    return `🍕 Para ${personas} personas necesitas ${pizzas} pizzas. Coste estimado: ${precioEstimado}€`;
};

console.log(resumenPizzaParty(10));
// "🍕 Para 10 personas necesitas 4 pizzas. Coste estimado: 48€"

console.log(resumenPizzaParty(25, 4));
// "🍕 Para 25 personas necesitas 13 pizzas. Coste estimado: 156€"

Ejemplo: conversor de temperatura

temperatura.js
const celsiusAFahrenheit = celsius => celsius * 9 / 5 + 32;
const fahrenheitACelsius = fahrenheit => (fahrenheit - 32) * 5 / 9;

console.log(celsiusAFahrenheit(0));    // 32
console.log(celsiusAFahrenheit(100));  // 212
console.log(fahrenheitACelsius(98.6)); // 37

// Función más completa con formato
const convertirTemperatura = (valor, de = "C") => {
    if (de === "C") {
        const resultado = celsiusAFahrenheit(valor);
        return `${valor}°C = ${resultado.toFixed(1)}°F`;
    }

    const resultado = fahrenheitACelsius(valor);
    return `${valor}°F = ${resultado.toFixed(1)}°C`;
};

console.log(convertirTemperatura(36.6));       // "36.6°C = 97.9°F"
console.log(convertirTemperatura(212, "F"));   // "212°F = 100.0°C"

Scope: dónde viven las variables

El scope (ámbito) determina dónde puedes acceder a una variable. Con let y const tienes block scope: la variable solo existe dentro del bloque { } donde la declaras.

scope.js
const nombre = "Global"; // Scope global — accesible en todas partes

const saludar = () => {
    const saludo = "Hola"; // Scope de función — solo dentro de saludar
    console.log(`${saludo}, ${nombre}`); // ✅ Funciona: accede a nombre (global)
};

saludar(); // "Hola, Global"
// console.log(saludo); // ❌ Error: saludo no existe aquí

// Block scope con let/const
if (true) {
    const mensaje = "Dentro del if";
    let contador = 1;
    console.log(mensaje); // ✅ "Dentro del if"
}
// console.log(mensaje);  // ❌ Error: mensaje no existe fuera del bloque
// console.log(contador); // ❌ Error: contador tampoco

// Por eso NUNCA uses var — tiene scope de función, no de bloque
if (true) {
    var fuga = "me escapo del bloque"; // ⚠️ var ignora el bloque
}
console.log(fuga); // "me escapo del bloque" — esto es un bug esperando a pasar

Closures: una función que recuerda

Un closure es cuando una función "recuerda" las variables del scope donde fue creada, incluso después de que ese scope haya terminado. Suena abstracto, pero mira este ejemplo:

closures.js
// Función que crea un contador privado
const crearContador = (inicio = 0) => {
    let cuenta = inicio; // Esta variable queda "encerrada"

    return {
        incrementar: () => ++cuenta,
        decrementar: () => --cuenta,
        valor: () => cuenta,
    };
};

const likes = crearContador();
likes.incrementar(); // 1
likes.incrementar(); // 2
likes.incrementar(); // 3
likes.decrementar(); // 2
console.log(likes.valor()); // 2

// Cada contador es independiente
const visitas = crearContador(100);
visitas.incrementar();
console.log(visitas.valor()); // 101
console.log(likes.valor());   // 2 — no se mezclan

La variable cuenta es privada: no puedes acceder a ella directamente desde fuera. Solo las funciones que devuelve crearContador pueden leerla y modificarla. Esto es un patrón muy útil para encapsular estado.

Funciones como parámetros (callbacks)

En JavaScript, las funciones son "ciudadanos de primera clase": puedes pasarlas como argumentos a otras funciones. Esto se llama callback y es fundamental (lo usarás constantemente con arrays):

callbacks.js
// Una función que recibe otra función como parámetro
const ejecutarDespuesDe = (segundos, callback) => {
    console.log(`Esperando ${segundos} segundos...`);
    setTimeout(callback, segundos * 1000);
};

ejecutarDespuesDe(2, () => {
    console.log("¡Han pasado 2 segundos!");
});

// Otro ejemplo: una función que aplica un descuento de la forma que tú quieras
const aplicarDescuento = (precio, estrategia) => {
    return estrategia(precio);
};

const descuentoPorcentaje = precio => precio * 0.8;  // 20% off
const descuentoFijo = precio => precio - 5;           // 5€ off
const descuentoVIP = precio => precio * 0.5;           // 50% off

console.log(aplicarDescuento(100, descuentoPorcentaje)); // 80
console.log(aplicarDescuento(100, descuentoFijo));       // 95
console.log(aplicarDescuento(100, descuentoVIP));        // 50

Resumen rápido

Tipo Sintaxis Hoisting Cuándo usarla
Declaration function nombre() {} Cuando necesitas hoisting
Expression const nombre = function() {} No Raro — las arrow son mejores
Arrow const nombre = () => {} No Tu opción por defecto
code

Crea tu kit de funciones útiles

Fácil schedule 15 min

Crea un archivo utils.js con estas funciones (todas con arrow functions):

  • calcularPizzas(personas, porciones, porcionesPorPizza) — con valores por defecto de 3 y 8. Devuelve el número de pizzas redondeado hacia arriba.
  • formatearPrecio(precio, moneda) — con moneda por defecto "€". Devuelve algo como "29.99€" con 2 decimales.
  • crearSaludo(idioma) — un closure que devuelve una función. Si el idioma es "es" saluda con "¡Hola!", si es "en" con "Hello!". El saludo debe incluir el nombre que le pases.
  • aplicarOperacion(a, b, operacion) — recibe dos números y un callback. Pruébala con funciones de sumar, restar y multiplicar.

Llama a cada función con diferentes argumentos y comprueba los resultados con console.log().

lightbulb Pistas

Para formatearPrecio, usa precio.toFixed(2) para asegurar los 2 decimales. Para el closure de crearSaludo, la función externa recibe el idioma y devuelve otra función que recibe el nombre: const saludoES = crearSaludo("es"); saludoES("Ana");.

Newsletter

Recibe nuevos cursos, actualizaciones, artículos del blog y promociones en tu correo.