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.
🎯 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.
// 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 llamarfunctiona 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.
function sumar() {
console.log(2 + 2);
}
sumar();
const sumar2 = function() {
console.log(3 + 3);
};
sumar2();
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.
sumar(); // se llama ANTES
function sumar() {
console.log(2 + 2);
}
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:
- 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.
- 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».
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:
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
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.
// 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
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?
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
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.
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.
function sumar(a, b) {
console.log(a + b);
}
sumar(2, 3); // imprime 5 y se acabo
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
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
totalempieza en 0 y esletporque va a cambiar.- Cada
agregarCarrito()suma su precio y devuelve el acumulado. - Ese valor se reasigna a
total, que va creciendo: 300, 400, 1000. 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.
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
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.
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
const aprendiendo = function() {
console.log('Aprendiendo JavaScript');
};
const aprendiendo = () => {
console.log('Aprendiendo JavaScript');
};
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:
const saludar = () => console.log('Hola');
saludar();
Cuando eliminas las llaves, el return se da por implicito:
// Las tres lineas siguientes hacen lo mismo
const duplicar1 = function(n) { return n * 2; };
const duplicar2 = (n) => { return n * 2; };
const duplicar3 = n => n * 2; // <-- return implicito
console.log(duplicar1(5));
console.log(duplicar2(5));
console.log(duplicar3(5));
Con un solo parametro, el parentesis tambien es opcional:
// UN parametro: parentesis opcional
const aprendiendo = tecnologia => console.log(`Aprendiendo ${tecnologia}`);
aprendiendo('JavaScript');
// DOS o mas: el parentesis es OBLIGATORIO
const aprendiendoDos = (tec1, tec2) => console.log(`Aprendiendo ${tec1} y ${tec2}`);
aprendiendoDos('JavaScript', 'Node.js');
// NINGUNO: tambien obligatorio, aunque este vacio
const saludar = () => console.log('Hola');
saludar();
| Situacion | Parentesis | Llaves | return |
|---|---|---|---|
| Sin parametros | Obligatorio () | Segun el cuerpo | Segun las llaves |
| Un parametro | Opcional | Segun el cuerpo | Segun las llaves |
| Dos o mas parametros | Obligatorio | Segun el cuerpo | Segun las llaves |
| Cuerpo de una linea | — | Opcionales | Implicito si no hay llaves |
| Cuerpo de varias lineas | — | Obligatorias | Debes escribirlo |
9.11 Arrow functions en la practica
En forEach y map
Este es el lugar donde mas las veras. Compara la evolucion:
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));
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
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
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
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);
Predice el hoisting
Sin ejecutar, di cual de estas dos llamadas funciona y cual falla, y por que.
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.
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
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');
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
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 losundefined. Solo para datos opcionales. returndevuelve 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.
setygetse usan sin parentesis. - Arrow functions:
functionse sustituye por=>tras el parentesis. Con un parametro el parentesis es opcional; con una linea, las llaves tambien y elreturnes 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 elreturn8, porque elreturnes implicito- Un error de sintaxis
'n * 2'como texto
🏁 Unidad completada