Volver al curso

JavaScript Desde Cero: Tu Primer Lenguaje de Programación

leccion
8 / 22
beginner
8 horas
Control de Flujo y Funciones

Funciones: Declaraciones, Expresiones y Arrow Functions

Lectura
45 min~9 min lectura

Funciones: Declaraciones, Expresiones y Arrow Functions

Objetivos de aprendizaje

Al finalizar esta lección serás capaz de:

  • Crear funciones usando declaraciones, expresiones y arrow functions
  • Entender parámetros, argumentos, valores por defecto y rest parameters
  • Trabajar con el valor de retorno (return) de forma efectiva
  • Distinguir cuándo usar cada tipo de función
  • Aplicar el principio de responsabilidad única para escribir funciones limpias

1. Declaración de funciones y sus variantes

Las funciones son bloques de código reutilizables. Son la base de cualquier programa bien organizado. En vez de repetir código, lo encapsulás en una función y la llamás cuando la necesites.

Declaración de función (function declaration)

La forma más clásica de crear funciones:

function saludar(nombre) {
  return `¡Hola, ${nombre}! Bienvenido a Cursalo.`;
}

console.log(saludar("Ana"));     // "¡Hola, Ana! Bienvenido a Cursalo."
console.log(saludar("Carlos"));  // "¡Hola, Carlos! Bienvenido a Cursalo."

Hoisting: Las declaraciones de función se "elevan" al inicio del scope, lo que significa que podés llamarlas antes de declararlas:

// ✅ Funciona gracias al hoisting
console.log(sumar(3, 5)); // 8

function sumar(a, b) {
  return a + b;
}

Expresión de función (function expression)

Asignás una función a una variable:

const multiplicar = function(a, b) {
  return a * b;
};

console.log(multiplicar(4, 7)); // 28

// ❌ NO tiene hoisting
// console.log(dividir(10, 2)); // ReferenceError
const dividir = function(a, b) {
  return a / b;
};

Arrow Function (=>)

Introducidas en ES6, son la forma más concisa de escribir funciones:

// Sintaxis completa
const saludar = (nombre) => {
  return `¡Hola, ${nombre}!`;
};

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

// Si el cuerpo es una sola expresión, podés omitir {} y return
const triplicar = n => n * 3;
const sumar = (a, b) => a + b;
const obtenerFecha = () => new Date().toLocaleDateString();

console.log(triplicar(5));     // 15
console.log(sumar(3, 7));      // 10
console.log(obtenerFecha());   // "21/2/2026"

// Retornar un objeto literal (necesita paréntesis extra)
const crearUsuario = (nombre, edad) => ({ nombre, edad, activo: true });
console.log(crearUsuario("Ana", 25)); // { nombre: "Ana", edad: 25, activo: true }

¿Cuándo usar cada una?

Tipo Usar cuando
Declaración (function nombre()) Funciones principales, que necesitan hoisting
Expresión (const f = function()) Callbacks, cuando querés evitar hoisting
Arrow (const f = () => {}) Callbacks cortos, métodos de array (.map, .filter), funciones simples
// Arrow functions brillan en métodos de array
const numeros = [1, 2, 3, 4, 5];

const dobles = numeros.map(n => n * 2);           // [2, 4, 6, 8, 10]
const pares = numeros.filter(n => n % 2 === 0);    // [2, 4]
const suma = numeros.reduce((acc, n) => acc + n, 0); // 15

// Encadenar métodos con arrows
const resultado = numeros
  .filter(n => n > 2)
  .map(n => n * 10)
  .reduce((acc, n) => acc + n, 0);
console.log(resultado); // 120 (30 + 40 + 50)

2. Parámetros y argumentos a fondo

Parámetros por defecto

function crearPerfil(nombre, edad = 18, pais = "Argentina") {
  return { nombre, edad, pais };
}

console.log(crearPerfil("Ana"));           // { nombre: "Ana", edad: 18, pais: "Argentina" }
console.log(crearPerfil("Carlos", 30));    // { nombre: "Carlos", edad: 30, pais: "Argentina" }
console.log(crearPerfil("Luis", 25, "México")); // { nombre: "Luis", edad: 25, pais: "México" }

// Los defaults pueden ser expresiones
function crearId(prefijo = "USR", numero = Math.floor(Math.random() * 10000)) {
  return `${prefijo}-${numero}`;
}
console.log(crearId());         // "USR-7423" (número aleatorio)
console.log(crearId("ADM"));    // "ADM-1856"

Rest parameters (...args)

Permiten recibir un número indefinido de argumentos como un array:

function sumarTodos(...numeros) {
  return numeros.reduce((total, n) => total + n, 0);
}

console.log(sumarTodos(1, 2, 3));           // 6
console.log(sumarTodos(10, 20, 30, 40, 50)); // 150

// Combinar parámetros normales con rest
function registrarVenta(vendedor, ...productos) {
  console.log(`Vendedor: ${vendedor}`);
  console.log(`Productos vendidos: ${productos.length}`);
  productos.forEach(p => console.log(` - ${p}`));
}

registrarVenta("María", "Laptop", "Mouse", "Teclado");

Destructuring en parámetros

// En vez de acceder a propiedades con punto
function mostrarUsuario({ nombre, edad, email = "no proporcionado" }) {
  console.log(`Nombre: ${nombre}`);
  console.log(`Edad: ${edad}`);
  console.log(`Email: ${email}`);
}

mostrarUsuario({ nombre: "Ana", edad: 28, email: "[email protected]" });
mostrarUsuario({ nombre: "Carlos", edad: 35 }); // email usa default

// Muy común en React y en configuraciones
function configurarApp({ tema = "oscuro", idioma = "es", notificaciones = true } = {}) {
  console.log(`Tema: ${tema}, Idioma: ${idioma}, Notif: ${notificaciones}`);
}

configurarApp();                          // Usa todos los defaults
configurarApp({ tema: "claro" });         // Solo cambia el tema
configurarApp({ idioma: "en", tema: "claro" }); // Cambia idioma y tema

3. Return, scope y closures

Return: devolviendo valores

// Una función sin return devuelve undefined
function sinReturn() {
  const x = 42;
}
console.log(sinReturn()); // undefined

// Return detiene la ejecución de la función
function verificarEdad(edad) {
  if (edad < 0) return "Edad inválida";
  if (edad < 18) return "Menor de edad";
  if (edad < 65) return "Adulto";
  return "Adulto mayor";
  
  // Este código nunca se ejecuta
  console.log("Esto nunca se imprime");
}

// Retornar múltiples valores (usando un objeto)
function calcularEstadisticas(numeros) {
  const suma = numeros.reduce((a, b) => a + b, 0);
  const promedio = suma / numeros.length;
  const minimo = Math.min(...numeros);
  const maximo = Math.max(...numeros);
  
  return { suma, promedio, minimo, maximo };
}

const stats = calcularEstadisticas([10, 20, 30, 40, 50]);
console.log(stats); // { suma: 150, promedio: 30, minimo: 10, maximo: 50 }

// Destructuring del return
const { promedio, maximo } = calcularEstadisticas([5, 10, 15]);
console.log(promedio); // 10
console.log(maximo);   // 15

Scope: alcance de las variables

// Scope global: visible en todo el programa
const globalVar = "Soy global";

function ejemplo() {
  // Scope de función: solo visible dentro de la función
  const funcionVar = "Soy de la función";
  console.log(globalVar);  // ✅ Accede a global
  console.log(funcionVar); // ✅ Accede a local
  
  if (true) {
    // Scope de bloque: solo visible dentro del if
    const bloqueVar = "Soy del bloque";
    console.log(bloqueVar);  // ✅
    console.log(funcionVar); // ✅
    console.log(globalVar);  // ✅
  }
  
  // console.log(bloqueVar); // ❌ ReferenceError
}

ejemplo();
// console.log(funcionVar); // ❌ ReferenceError

Closures: funciones que recuerdan

Un closure es una función que "recuerda" las variables del scope donde fue creada, incluso después de que ese scope haya terminado:

function crearContador() {
  let cuenta = 0; // Esta variable queda "encerrada" en el closure
  
  return {
    incrementar: () => ++cuenta,
    decrementar: () => --cuenta,
    obtenerValor: () => cuenta,
  };
}

const contador = crearContador();
console.log(contador.incrementar()); // 1
console.log(contador.incrementar()); // 2
console.log(contador.incrementar()); // 3
console.log(contador.decrementar()); // 2
console.log(contador.obtenerValor()); // 2

// Cada instancia tiene su propia cuenta
const otroContador = crearContador();
console.log(otroContador.obtenerValor()); // 0 (independiente)

// Closure práctico: crear funciones multiplicadoras
function crearMultiplicador(factor) {
  return (numero) => numero * factor;
}

const duplicar = crearMultiplicador(2);
const triplicar = crearMultiplicador(3);
const porcentaje = crearMultiplicador(0.01);

console.log(duplicar(5));     // 10
console.log(triplicar(5));    // 15
console.log(porcentaje(150)); // 1.5

Funciones de orden superior

Funciones que reciben o devuelven otras funciones:

// Función que recibe otra función como parámetro
function aplicarOperacion(numeros, operacion) {
  return numeros.map(operacion);
}

const nums = [1, 2, 3, 4, 5];
console.log(aplicarOperacion(nums, n => n * 2));    // [2, 4, 6, 8, 10]
console.log(aplicarOperacion(nums, n => n ** 2));   // [1, 4, 9, 16, 25]
console.log(aplicarOperacion(nums, n => `$${n}`));  // ["$1", "$2", "$3", "$4", "$5"]

// Función que retorna una función (factory)
function crearValidador(min, max) {
  return (valor) => valor >= min && valor <= max;
}

const esEdadValida = crearValidador(0, 120);
const esPrecioValido = crearValidador(0.01, 999999);
const esNotaValida = crearValidador(1, 10);

console.log(esEdadValida(25));     // true
console.log(esEdadValida(-5));     // false
console.log(esPrecioValido(0));    // false
console.log(esNotaValida(7));      // true

Errores comunes de principiantes
  1. Olvidar el return: Sin return, la función devuelve undefined. Si tu función debe producir un resultado, siempre usá return.

  2. Confundir parámetros con argumentos: Los parámetros son las variables en la definición; los argumentos son los valores que pasás al llamar la función.

  3. Arrow functions con cuerpo de una línea y {}: Si usás {} en una arrow, necesitás return explícito. n => n * 2 funciona, pero n => { n * 2 } devuelve undefined.

  4. Mutar datos pasados como argumento: Los objetos y arrays se pasan por referencia. Si modificás un objeto dentro de una función, el original también cambia.

  5. Funciones demasiado largas: Si tu función tiene más de 20-30 líneas, probablemente hace demasiadas cosas. Dividila en funciones más pequeñas.


Puntos clave de esta lección
  1. Las declaraciones de función tienen hoisting; las expresiones y arrows no.
  2. Las arrow functions son ideales para callbacks y funciones cortas.
  3. Los parámetros por defecto evitan undefined cuando no se pasan argumentos.
  4. Rest parameters (...args) permiten recibir un número variable de argumentos.
  5. El scope determina dónde son accesibles las variables (global > función > bloque).
  6. Los closures permiten que funciones "recuerden" variables de su scope de creación.
  7. Las funciones de orden superior reciben o devuelven funciones — son clave en JavaScript moderno.

Quiz de autoevaluación

1. ¿Qué devuelve una función sin return?
a) null
b) 0
c) undefined
d) false

2. ¿Cuál es la sintaxis correcta de una arrow function con un parámetro y retorno implícito?
a) (n) => { n * 2 }
b) n => n * 2
c) n -> n * 2
d) function(n) => n * 2

3. ¿Qué es un closure?
a) Una función que se cierra después de ejecutarse
b) Una función que recuerda las variables de su scope de creación
c) Un tipo de loop
d) Un error de JavaScript

4. ¿Qué permite rest parameters (...)?
a) Pausar la ejecución
b) Recibir un número indefinido de argumentos como array
c) Expandir un array
d) Crear variables globales

5. ¿Las declaraciones de función tienen hoisting?
a) Sí, se pueden llamar antes de declararlas
b) No, deben declararse antes de llamarlas
c) Solo con const
d) Solo en modo estricto

Respuestas: 1-c, 2-b, 3-b, 4-b, 5-a


💡 Concepto Clave

Revisemos los puntos más importantes de esta lección antes de continuar.

Ejercicio práctico

Misión: Mini-framework de validación

Creá un conjunto de funciones que formen un mini-sistema de validación reutilizable:

  1. crearValidador(reglas) — función factory que recibe un array de reglas y retorna una función validadora
  2. Cada regla es un objeto { campo, validar: (valor) => boolean, mensaje }
  3. La función retornada recibe un objeto de datos y retorna { esValido, errores }
  4. Creá validadores para: requerido, email, mínimo de caracteres, solo números
// Resultado esperado:
const validarRegistro = crearValidador([
  { campo: "nombre", validar: v => v && v.length >= 2, mensaje: "Nombre muy corto" },
  { campo: "email", validar: v => v && v.includes("@"), mensaje: "Email inválido" },
  { campo: "edad", validar: v => v >= 13 && v <= 120, mensaje: "Edad fuera de rango" },
]);

const resultado = validarRegistro({ nombre: "A", email: "test", edad: 10 });
console.log(resultado);
// { esValido: false, errores: ["Nombre muy corto", "Email inválido", "Edad fuera de rango"] }
🧠 Pon a prueba tu conocimiento
¿Cuál es el aspecto más importante que aprendiste en esta lección?
  • Comprendo el concepto principal y puedo explicarlo con mis palabras
  • Entiendo cómo aplicarlo en mi situación específica
  • Necesito repasar algunas partes antes de continuar
  • Quiero ver más ejemplos prácticos del tema
✅ ¡Excelente! Continúa con la siguiente lección para profundizar más.