1. Funciones: tres formas de escribir lo mismo
Una función es un trozo de código con nombre que recibe datos, hace algo con ellos y, si quieres, devuelve un resultado. Sirve para no repetirte y para dar nombre a las ideas: calcularIva(precio) se entiende mucho mejor que la fórmula escrita en mitad de otro cálculo.
1// 1. Declaración: se puede llamar incluso antes de la línea donde está escrita
2function precioConIva(precio, iva = 0.21) {
3 return precio * (1 + iva);
4}
5
6// 2. Expresión: una función guardada en una variable
7const saludar = function (nombre) {
8 return "Hola, " + nombre;
9};
10
11// 3. Función flecha: la forma corta, ideal para funciones pequeñas
12const doble = (n) => n * 2;
13const sumarTodos = (...numeros) => numeros.reduce((a, b) => a + b, 0);Las tres hacen lo mismo en la mayoría de casos. La diferencia práctica es que las declaraciones se pueden usar antes de escribirse (el motor las «eleva» al principio, lo que se llama hoisting) y que las funciones flecha no tienen su propio this, algo que importa dentro de objetos y que veremos más abajo. En el día a día, usa declaraciones para las funciones importantes de tu programa y flechas para los callbacks cortos.
Fíjate en dos detalles de los parámetros. Pueden tener un valor por defecto (iva = 0.21), que se usa cuando no se pasa ese argumento. Y el parámetro rest (...numeros) recoge todos los argumentos que sobren en un array, lo que permite escribir funciones que aceptan cualquier cantidad de valores.
2. Ámbito y closures
Cada bloque de llaves crea un ámbito: las variables declaradas dentro con let o const solo existen ahí. Una función puede leer las variables de los ámbitos que la rodean, pero no al revés. Esto parece un detalle técnico hasta que descubres su consecuencia más interesante: los closures.
Un closure es una función con mochila
1function crearSaludo(saludo) {
2 return (nombre) => saludo + ", " + nombre; // la flecha recuerda «saludo»
3}
4
5const hola = crearSaludo("Hola");
6const buenas = crearSaludo("Buenas tardes");
7hola("Ana"); // "Hola, Ana"
8buenas("Luis"); // "Buenas tardes, Luis"Los closures están por todas partes aunque no los nombres: cada vez que un manejador de eventos usa una variable declarada fuera de él, o un setTimeout usa una variable del código que lo programó, hay un closure trabajando. Entenderlos es entender por qué tu código funciona (o por qué no).
3. Funciones que reciben funciones
En JavaScript las funciones son valores como cualquier otro: se guardan en variables, se meten en arrays y se pasan como argumento. Una función que se pasa a otra para que la llame cuando toque se llama callback, y una función que recibe o devuelve funciones se llama de orden superior. map, filter, addEventListener y setTimeout son de orden superior: tú les dices qué hacer y ellas deciden cuándo.
1function repetir(veces, accion) {
2 for (let i = 1; i <= veces; i++) accion(i);
3}
4
5repetir(3, (n) => console.log("Vuelta", n));
6
7const botones = document.querySelectorAll("button");
8botones.forEach((boton) => boton.addEventListener("click", () => boton.classList.toggle("activo")));4. Objetos
Un objeto agrupa datos relacionados bajo un mismo nombre, como una ficha: cada dato es una propiedad con su clave y su valor, y las funciones que guarda se llaman métodos. Se accede a las propiedades con punto (alumno.nombre) o con corchetes cuando la clave está en una variable (alumno[campo]).
1const nombre = "Ana";
2const alumno = {
3 nombre, // atajo de { nombre: nombre }
4 notas: [7, 8, 6],
5 media() {
6 return this.notas.reduce((a, b) => a + b) / this.notas.length;
7 },
8};
9
10const { notas, media } = alumno; // desestructuración
11const copia = { ...alumno, curso: "2º DAW" }; // propagación: copia y añade
12const datos = structuredClone({ nombre, notas }); // copia profunda (no admite funciones)Copiar no es lo mismo que asignar
Esta es probablemente la fuente de errores más frecuente con objetos. Una variable no contiene el objeto, sino una referencia a él. Si haces const b = a, a y b apuntan al mismo objeto y cambiar uno cambia el otro. La propagación { ...a } crea un objeto nuevo, pero solo copia el primer nivel: si dentro hay un array, ese array sigue siendo compartido. Para una copia completa usa structuredClone(a).
Quién es this
Dentro de un método, this es el objeto sobre el que se llamó: en alumno.media(), this es alumno. Las funciones flecha no tienen this propio, sino que usan el del código que las rodea. Por eso no se usan flechas para definir métodos de un objeto, pero sí dentro de ellos, por ejemplo en el callback de un map, donde queremos seguir teniendo el this del método.
5. Clases
Cuando necesitas crear muchos objetos con la misma forma y el mismo comportamiento, defines una clase. Si vienes de Java, la sintaxis te resultará familiar, aunque por debajo JavaScript funciona con prototipos: una clase es una forma más cómoda de escribir lo mismo.
1class Cuenta {
2 #saldo = 0; // campo privado: inaccesible desde fuera
3 static comision = 0.5; // pertenece a la clase, no a cada objeto
4
5 constructor(titular) {
6 this.titular = titular;
7 }
8
9 ingresar(cantidad) {
10 if (cantidad <= 0) throw new Error("La cantidad debe ser positiva");
11 this.#saldo += cantidad;
12 }
13
14 get saldo() { // se lee como una propiedad: cuenta.saldo
15 return this.#saldo;
16 }
17}
18
19class CuentaJoven extends Cuenta {
20 ingresar(cantidad) {
21 super.ingresar(cantidad + 1); // regalo de 1 € por ingreso
22 }
23}
24
25const cuenta = new CuentaJoven("Lucía");
26cuenta.ingresar(50);
27console.log(cuenta.saldo); // 51- constructor se ejecuta al hacer
newy prepara el objeto. - #campo es privado de verdad: fuera de la clase ni siquiera se puede leer.
- get / set permiten que un cálculo se use como si fuera una propiedad.
- static define miembros de la clase, útiles para constantes y funciones auxiliares.
- extends y super sirven para heredar y reutilizar el comportamiento del padre.
6. Módulos, Map y Set
Cuando un programa crece, meterlo todo en un fichero se vuelve inmanejable. Los módulos permiten dividirlo: cada fichero exporta lo que quiere compartir y los demás lo importan. En el navegador se activan con <script type="module">, y cada módulo tiene su propio ámbito, así que sus variables no chocan con las de otros ficheros.
1// carrito.js
2export const IVA = 0.21;
3export function total(lineas) { /* … */ }
4export default class Carrito { /* … */ }
5
6// app.js
7import Carrito, { IVA, total } from "./carrito.js";Por último, dos estructuras que complementan a arrays y objetos. Un Set es una colección sin repetidos: [...new Set(lista)] es la forma más corta de quitar duplicados de un array. Un Map es como un objeto pensado para usarse como diccionario: sus claves pueden ser de cualquier tipo, mantiene el orden de inserción y tiene métodos claros (set, get, has, delete, size).
| Necesito… | Usa |
|---|---|
| Una lista ordenada con posibles repetidos | Array |
| Valores únicos, o saber rápido si algo está | Set |
| Un diccionario clave → valor que cambia mucho | Map |
| Una ficha con propiedades fijas (un usuario, un producto) | Objeto |
Menos clases de las que crees
7. Retos
Los dos primeros practican closures y reduce; el tercero, clases con campos privados; y el último junta closures y temporizadores en una técnica que se usa en todos los buscadores.
Completa crearContador(inicio) para que devuelva un objeto con tres métodos: incrementar(), decrementar() y valor(). Si no se indica inicio, empieza en 0. Cada contador creado debe llevar su propia cuenta, y el valor no debe poder cambiarse desde fuera sin usar los métodos.
Completa agruparPor(lista, clave), que recibe un array de objetos y el nombre de una propiedad, y devuelve un objeto cuyas claves son los valores de esa propiedad y cuyos valores son arrays con los elementos correspondientes, en su orden original.
Completa la clase Carrito: agregar(nombre, precio, cantidad = 1) añade un producto o, si ya está, suma la cantidad; eliminar(nombre) lo quita; el getter total devuelve el importe redondeado a 2 decimales (número) y el getter unidades devuelve el número total de unidades. Las líneas deben guardarse en un campo privado #lineas.
En un buscador no se quiere lanzar una búsqueda por cada tecla. Completa debounce(fn, ms), que devuelve una nueva función: cada vez que se llama, retrasa la ejecución de fn hasta que pasen ms milisegundos sin nuevas llamadas, y entonces ejecuta fn una sola vez con los argumentos de la última llamada.