Saltar al contenido
Curso de JavaScriptModerno · Interactivo

Unidad 9 · Lecciones 058-070

Funciones en JavaScript

Un buen programador no es quien escribe mucho codigo, sino quien sabe dividir un problema grande en piezas pequenas. Las funciones son esa herramienta: bloques de codigo con nombre, reutilizables, que reciben datos y devuelven resultados.

📚 13 lecciones originales ⏱ Lectura ~45 min ↩ Requiere: Unidades 7 y 8

🎯 Objetivo de aprendizaje

Al terminar sabras declarar funciones de las dos formas existentes, explicar el hoisting, pasarles parametros con valores por defecto, hacer que devuelvan valores, comunicarlas entre si, anadirlas a un objeto y reescribirlas como arrow functions.

9.1 Declaracion de funcion

Una funcion es una serie de instrucciones que realizan una accion. Sus dos grandes ventajas: ordenan el codigo y son reutilizables.

Declaracion de funcion (function declaration)
// 1. DEFINIR la funcion
function sumar() {
    console.log(2 + 2);
}

// 2. MANDARLA LLAMAR (si no haces esto, no pasa nada)
sumar();

// Es reutilizable: llamala tantas veces como quieras
sumar();
sumar();

Anatomia

  • function — palabra reservada. No puedes llamar function a una variable.
  • sumar — el nombre. Mismas reglas que las variables: no puede empezar por numero, se usa camelCase.
  • () — los parentesis. Aqui iran los parametros (seccion 9.5).
  • { } — el cuerpo de la funcion: todo lo que hace, delimitado por las llaves.

⚠ Definir no es llamar

Escribir sumar sin parentesis no hace nada. Para que la funcion se ejecute necesitas sumar(), con los parentesis.

💡 Casos de uso reales

Una funcion que valide un formulario la reutilizas en todos tus formularios. Una funcion que compruebe si un usuario esta autenticado la usas en todas las secciones del sitio. Esa es la idea: escribirlo una vez, usarlo muchas.

9.2 Expresion de funcion

La segunda forma de crear funciones: se parece a declarar una variable, pero su valor es una funcion.

Declaracion
function sumar() {
    console.log(2 + 2);
}

sumar();
Expresion
const sumar2 = function() {
    console.log(3 + 3);
};

sumar2();
Las dos juntas
function sumar() {
    console.log(2 + 2);
}

const sumar2 = function() {
    console.log(3 + 3);
};

sumar();    // 4
sumar2();   // 6

Como ves, se mandan llamar exactamente igual. La forma de definirlas es lo unico que cambia... a simple vista.

9.3 La diferencia real: el hoisting

🎓 Pregunta de entrevista clasica

¿Se puede usar una funcion antes de escribirla? La respuesta depende de como la hayas creado.

✓ Declaracion: si funciona
sumar();   // se llama ANTES

function sumar() {
    console.log(2 + 2);
}
✗ Expresion: falla
sumar2();  // se llama ANTES

const sumar2 = function() {
    console.log(3 + 3);
};

Por que

JavaScript lee tu archivo en dos pasadas. Ese comportamiento se llama hoisting:

  1. Etapa de creacion. Recorre el documento y registra todas las funciones declaradas y todas las variables que van a existir. Una function declaration queda ya completamente registrada aqui.
  2. Etapa de ejecucion. Ahora si corre el codigo linea a linea. Cuando llega a sumar(), esa funcion ya existe desde la primera pasada.

Con la expresion de funcion, lo que se registra en la primera pasada es la variable, todavia sin valor. Al llamarla antes de la linea donde se le asigna la funcion, obtienes el error «Cannot access 'sumar2' before initialization».

Lo que JavaScript "ve" en la primera pasada
function sumar() { ... }   // ya registrada y utilizable

const sumar2;              // registrada, pero SIN valor todavia

💡 En la practica

En rendimiento no hay diferencia apreciable entre ambas. Usa la que prefieras, pero manten la coherencia dentro de un mismo proyecto y, en general, define las funciones antes de usarlas: el codigo se lee mejor.

9.4 Funciones nativas y la diferencia entre funcion y metodo

JavaScript trae miles de funciones ya escritas. No hay una «libreria estandar» formal como en Java o Python, pero forman parte del nucleo del lenguaje y ya has usado varias:

Funciones que ya conoces
alert('Hubo un error');              // ventana de aviso
prompt('Cual es tu edad?');          // ventana con campo de texto
Number.parseInt('20');               // convertir texto a numero
console.log('Hola');                 // escribir en la consola

Funcion o metodo: solo cambia el contexto

La diferencia esta en el punto
const numero1 = 20;      // numero
const numero2 = '20';    // string

// FUNCION: nombre + parentesis, el valor va DENTRO
console.log(Number.parseInt(numero2));

// METODO: variable + punto + nombre + parentesis
console.log(numero1.toString());
Funcion
nombre(valor)
Se llama por su nombre y recibe los datos entre parentesis.
Metodo
dato.nombre()
Pertenece a un dato concreto y se llama con la sintaxis de punto.

Al final ambas hacen lo mismo: ejecutar codigo reutilizable. El nombre depende solo de donde vive esa funcion.

9.5 Parametros y argumentos

La funcion sumar() que hicimos siempre suma 2 + 2. Es inutil. Para que una funcion sea util necesita recibir datos distintos cada vez.

Una funcion que suma lo que le pases
// a y b son PARAMETROS: variables que la funcion espera recibir
function sumar(a, b) {
    console.log(a + b);
}

// 2 y 3 son ARGUMENTOS: los valores reales que le pasas
sumar(2, 3);           // 5
sumar(200, 184);       // 384
sumar(123, 321);       // 444
Parametro
La variable que aparece en la definicion de la funcion. Es un hueco a rellenar.
Argumento
El valor real que pasas al llamar a la funcion.

Un ejemplo mas cercano a la realidad

Saludar a cualquier persona
function saludar(nombre, apellido) {
    console.log(`Hola ${nombre} ${apellido}`);
}

saludar('Juan', 'de la Torre');
saludar('Ana', 'Lopez');

No puedes escribir el nombre fijo dentro de la funcion porque no todas las personas se llaman igual. Twitter y Facebook usan exactamente esta logica para mostrar tu nombre.

¿Y si falta un argumento?

Argumentos que faltan
function saludar(nombre, apellido) {
    console.log(`Hola ${nombre} ${apellido}`);
}

saludar('Juan', 'de la Torre');   // Hola Juan de la Torre
saludar('Juan');                  // Hola Juan undefined
saludar();                        // Hola undefined undefined

El parametro existe pero no tiene valor, asi que vale undefined. Feo. La solucion, a continuacion.

9.6 Parametros por defecto

Valores de reserva
function saludar(nombre = 'Desconocido', apellido = '') {
    console.log(`Hola ${nombre} ${apellido}`);
}

saludar('Juan', 'de la Torre');   // Hola Juan de la Torre
saludar('Juan');                  // Hola Juan            (apellido vacio)
saludar();                        // Hola Desconocido

Como funciona

  • Si al llamar a la funcion si pasas el argumento, el valor por defecto se ignora.
  • Si no lo pasas, se usa el valor por defecto en su lugar.

💡 Cuando usarlos y cuando no

Son perfectos para datos opcionales: un apellido, una imagen de perfil, un idioma preferido. No los uses para datos obligatorios. Si trabajas en un sistema bancario, la cuenta de destino y el importe no pueden tener valor por defecto: si faltan, el programa debe avisar del error.

9.7 Como se comunican las funciones

Un proyecto real tiene decenas o cientos de funciones. En lugar de una funcion gigante de 600 lineas, se escriben funciones pequenas que se llaman unas a otras.

Funciones encadenadas
function iniciarApp() {
    console.log('Iniciando App...');
    segundaFuncion();          // al terminar, llama a la siguiente
}

function segundaFuncion() {
    console.log('Desde la segunda funcion');
    usuarioAutenticado('Pablo');
}

function usuarioAutenticado(usuario) {
    console.log('Autenticando usuario, espere...');
    console.log(`Usuario ${usuario} autenticado exitosamente`);
}

// Una sola llamada arranca toda la cadena
iniciarApp();

💡 El patron

Siempre hay una funcion que arranca todo, como el boton de encendido de un ordenador. Desde ahi se van llamando las demas. Fijate ademas en que el nombre del usuario viaja de una funcion a otra como argumento: al iniciar la aplicacion no sabes quien entrara, lo descubres mientras se ejecuta.

9.8 Funciones que devuelven valores: return

Hasta ahora nuestras funciones escribian en la consola y ahi se acababa todo. Pero muchas veces quieres hacer algo con el resultado.

Sin return: el resultado se pierde
function sumar(a, b) {
    console.log(a + b);
}

sumar(2, 3);   // imprime 5 y se acabo
Con return: el resultado se guarda
function sumar(a, b) {
    return a + b;
}

const resultado = sumar(2, 3);
console.log(resultado);   // 5, y puedo reutilizarlo

⚠ Un return necesita destino

Si una funcion retorna un valor pero no lo asignas a nada, ese valor se pierde. Siempre que uses return, guarda el resultado en una variable o pasalo a otra funcion.

Ejemplo completo: carrito con impuesto

Dos funciones que se pasan valores
let total = 0;

// Suma un precio al total y devuelve el nuevo total
function agregarCarrito(precio) {
    return total += precio;
}

// Aplica un 15% de impuesto sobre el total
function calcularImpuesto(total) {
    return total * 1.15;
}

total = agregarCarrito(300);
total = agregarCarrito(100);
total = agregarCarrito(600);

console.log(`Total: ${total}`);

const totalPagar = calcularImpuesto(total);
console.log(`El total a pagar es de ${totalPagar}`);

Que ocurre paso a paso

  1. total empieza en 0 y es let porque va a cambiar.
  2. Cada agregarCarrito() suma su precio y devuelve el acumulado.
  3. Ese valor se reasigna a total, que va creciendo: 300, 400, 1000.
  4. calcularImpuesto(total) recibe los 1000 y devuelve 1150.

Este estilo (una variable que va pasando por funciones que la transforman) es la base de muchos proyectos reales.

9.9 Metodos de propiedad: funciones dentro de un objeto

Ya lo viste en la Unidad 7 con this. Ahora que conoces los parametros, veamos un ejemplo completo: un reproductor de musica.

Un reproductor como objeto
const reproductor = {
    cancion: '',

    reproducir: function(id) {
        console.log(`Reproduciendo cancion con el id ${id}`);
    },
    pausar: function() {
        console.log('Pausando...');
    },
    borrar: function(id) {
        console.log(`Borrando cancion con el id ${id}`);
    },
    crearPlaylist: function(nombre) {
        console.log(`Creando la playlist ${nombre}`);
    },
    reproducirPlaylist: function(nombre) {
        console.log(`Reproduciendo la playlist ${nombre}`);
    }
};

reproductor.reproducir(30);
reproductor.pausar();
reproductor.borrar(30);
reproductor.crearPlaylist('Heavy Metal');
reproductor.reproducirPlaylist('Heavy Metal');

💡 Por que agrupar asi

Todas las funciones relacionadas con el reproductor viven en un solo objeto. Escribes reproductor. y el editor te ofrece todo lo que puedes hacer. En proyectos grandes esto mantiene el codigo organizado y evita nombres sueltos por todas partes.

Anadir metodos desde fuera

Igual que con las propiedades normales
const reproductor = {
    reproducir: function(id) {
        console.log(`Reproduciendo cancion con el id ${id}`);
    }
};

// Anadir un metodo despues de crear el objeto
reproductor.borrar = function(id) {
    console.log(`Borrando cancion con el id ${id}`);
};

reproductor.reproducir(20);
reproductor.borrar(20);

set y get

Terminos universales en programacion: set guarda un valor y get lo recupera. JavaScript tiene sintaxis propia para ellos.

Un setter y un getter
const reproductor = {
    cancion: '',

    set nuevaCancion(cancion) {
        this.cancion = cancion;
        console.log(`Anadiendo ${cancion}`);
    },

    get obtenerCancion() {
        console.log(`${this.cancion}`);
    }
};

// El setter se usa como si asignaras una propiedad, SIN parentesis
reproductor.nuevaCancion = 'Enter Sandman';

// El getter tampoco lleva parentesis
reproductor.obtenerCancion;

⚠ Sin parentesis

Esa es la peculiaridad: aunque son funciones, al haberlas definido con set y get se usan como si fueran propiedades normales.

9.10 Arrow Functions

Las arrow functions (funciones de flecha) llegaron con ECMAScript 6 y cambiaron por completo la forma de escribir JavaScript. Son una sintaxis mas corta para lo mismo.

La transformacion, paso a paso

Paso 0: expresion de funcion normal
const aprendiendo = function() {
    console.log('Aprendiendo JavaScript');
};
Paso 1: quitar function y poner la flecha detras del parentesis
const aprendiendo = () => {
    console.log('Aprendiendo JavaScript');
};
Paso 2: con una sola linea, las llaves sobran
const aprendiendo = () => console.log('Aprendiendo JavaScript');

aprendiendo();

💡 La regla de conversion

La palabra function desaparece y se sustituye por => colocada a la derecha del parentesis. El parentesis se mantiene, porque ahi siguen yendo los parametros.

Las tres ventajas

Si el cuerpo tiene una sola linea, puedes eliminar las llaves:

Sin llaves
const saludar = () => console.log('Hola');
saludar();
SituacionParentesisLlavesreturn
Sin parametrosObligatorio ()Segun el cuerpoSegun las llaves
Un parametroOpcionalSegun el cuerpoSegun las llaves
Dos o mas parametrosObligatorioSegun el cuerpoSegun las llaves
Cuerpo de una linea—OpcionalesImplicito si no hay llaves
Cuerpo de varias lineas—ObligatoriasDebes escribirlo

9.11 Arrow functions en la practica

En forEach y map

Este es el lugar donde mas las veras. Compara la evolucion:

De function a arrow, en tres pasos
const carrito = [
    { nombre: 'Monitor 27 pulgadas', precio: 500 },
    { nombre: 'Tablet', precio: 100 },
    { nombre: 'Celular', precio: 800 }
];

// Paso 0: funcion tradicional
carrito.forEach(function(producto) {
    console.log(producto.nombre);
});

// Paso 1: arrow function
carrito.forEach((producto) => {
    console.log(producto.nombre);
});

// Paso 2: sin parentesis (un parametro) y sin llaves (una linea)
carrito.forEach(producto => console.log(producto.nombre));
map con return implicito
const carrito = [
    { nombre: 'Monitor 27 pulgadas', precio: 500 },
    { nombre: 'Tablet', precio: 100 }
];

// Version larga
const nuevo1 = carrito.map(function(producto) {
    return `${producto.nombre} - precio: ${producto.precio}`;
});

// Version corta: sin parentesis, sin llaves, sin return
const nuevo2 = carrito.map(producto => `${producto.nombre} - precio: ${producto.precio}`);

console.log(nuevo1);
console.log(nuevo2);

El reproductor, reescrito

Metodos de propiedad con arrow functions
const reproductor = {
    reproducir: id => console.log(`Reproduciendo cancion con el id ${id}`),
    pausar: () => console.log('Pausando...'),
    borrar: id => console.log(`Borrando cancion con el id ${id}`),
    crearPlaylist: nombre => console.log(`Creando la playlist ${nombre}`),
    reproducirPlaylist: nombre => console.log(`Reproduciendo la playlist ${nombre}`)
};

reproductor.reproducir(30);
reproductor.pausar();
reproductor.crearPlaylist('Rock de los 90');

Compara este objeto con el de la seccion 9.9: hace exactamente lo mismo con la mitad de lineas y se lee de un vistazo.

⚠ Una excepcion importante

Las arrow functions manejan this de forma distinta a las funciones normales. En un metodo que necesite this para acceder a otras propiedades del objeto, usa la sintaxis tradicional function() { }. En los ejemplos de arriba no se usa this, por eso funcionan perfectamente.

9.12 Ejercicios practicos

Ejercicio 1

Calculadora de envio

Crea una funcion calcularEnvio(peso, destino) donde destino valga 'nacional' por defecto. Que devuelva el peso multiplicado por 5. Llamala con y sin el segundo argumento.

Ver solucion
Solucion
function calcularEnvio(peso, destino = 'nacional') {
    const costo = peso * 5;
    console.log(`Envio ${destino} de ${peso}kg: ${costo}`);
    return costo;
}

const envio1 = calcularEnvio(3);
const envio2 = calcularEnvio(3, 'internacional');

console.log('Total de ambos envios:', envio1 + envio2);
Ejercicio 2

Predice el hoisting

Sin ejecutar, di cual de estas dos llamadas funciona y cual falla, y por que.

Punto de partida
primera();
segunda();

function primera() {
    console.log('Soy la primera');
}

const segunda = function() {
    console.log('Soy la segunda');
};
Ver respuesta

primera() funciona: es una declaracion de funcion y queda registrada en la etapa de creacion del hoisting.

segunda() falla con «Cannot access 'segunda' before initialization»: es una expresion de funcion, y en la etapa de creacion solo se registra la variable, todavia sin valor.

De hecho, el error en segunda() ocurre antes de llegar siquiera a ejecutar primera() si las llamadas estan en ese orden.

Reto 1

Amplia el reproductor

Partiendo del reproductor de la seccion 9.9, anadele dos metodos mas: crearPlaylist(nombre) y reproducirPlaylist(nombre). Escribelos con arrow functions y pruebalos con dos playlists distintas.

Este reto aparece literalmente en el material original del curso.

Ver solucion
Solucion
const reproductor = {
    reproducir: id => console.log(`Reproduciendo cancion con el id ${id}`),
    pausar: () => console.log('Pausando...'),
    borrar: id => console.log(`Borrando cancion con el id ${id}`),
    crearPlaylist: nombre => console.log(`Creando la playlist ${nombre}`),
    reproducirPlaylist: nombre => console.log(`Reproduciendo la playlist ${nombre}`)
};

reproductor.crearPlaylist('Heavy Metal');
reproductor.crearPlaylist('Rock de los 90');
reproductor.reproducirPlaylist('Heavy Metal');
Reto 2

Flujo completo de un pedido

Escribe tres funciones que se comuniquen entre si: calcularSubtotal(carrito) que sume los precios y lo devuelva, aplicarDescuento(subtotal, porcentaje) con un 10% por defecto, y mostrarTotal(total) que lo imprima con dos decimales. Encadenalas desde una funcion procesarPedido().

Ver solucion
Solucion
const carrito = [
    { nombre: 'Monitor', precio: 500 },
    { nombre: 'Teclado', precio: 50 },
    { nombre: 'Celular', precio: 800 }
];

function calcularSubtotal(carrito) {
    let subtotal = 0;
    carrito.forEach(producto => subtotal += producto.precio);
    return subtotal;
}

function aplicarDescuento(subtotal, porcentaje = 10) {
    const descuento = subtotal * (porcentaje / 100);
    console.log(`Descuento del ${porcentaje}%: ${descuento}`);
    return subtotal - descuento;
}

function mostrarTotal(total) {
    console.log(`Total a pagar: ${total.toFixed(2)}`);
}

function procesarPedido(carrito) {
    const subtotal = calcularSubtotal(carrito);
    console.log(`Subtotal: ${subtotal}`);

    const total = aplicarDescuento(subtotal);
    mostrarTotal(total);
}

procesarPedido(carrito);

Fijate en que procesarPedido no calcula nada: solo coordina. Cada funcion hace una cosa y le pasa el resultado a la siguiente. Asi se estructura el codigo en proyectos reales.

9.13 Resumen de lo aprendido

  • Dos formas de crear funciones: declaracion (function nombre() {}) y expresion (const nombre = function() {}). Se llaman igual.
  • Definir no es llamar: sin los parentesis () la funcion no se ejecuta.
  • Hoisting: JavaScript lee el archivo en dos pasadas. Las declaraciones quedan registradas antes; las expresiones no.
  • Funcion o metodo: nombre() es funcion; dato.nombre() es metodo. Hacen lo mismo.
  • Parametros son los huecos de la definicion; argumentos, los valores reales que pasas.
  • Parametros por defecto: function f(a = 'valor') evita los undefined. Solo para datos opcionales.
  • return devuelve un valor; guardalo en una variable o se pierde.
  • Las funciones se llaman entre si. Siempre hay una que arranca todo.
  • Metodos de propiedad: funciones dentro de un objeto. set y get se usan sin parentesis.
  • Arrow functions: function se sustituye por => tras el parentesis. Con un parametro el parentesis es opcional; con una linea, las llaves tambien y el return es implicito.

9.14 Verificacion de cierre

¿Cual de estas se puede llamar antes de la linea donde se define?

  • function sumar() { }
  • const sumar = function() { };
  • const sumar = () => { };
  • Ninguna de las tres

En una arrow function, ¿cuando es opcional el parentesis de los parametros?

  • Siempre
  • Cuando no hay ningun parametro
  • Cuando hay exactamente un parametro
  • Cuando hay dos o mas parametros

¿Que devuelve const doble = n => n * 2; al llamar doble(4)?

  • undefined, porque falta el return
  • 8, porque el return es implicito
  • Un error de sintaxis
  • 'n * 2' como texto

🏁 Unidad completada