Arrays y objetos
Hasta ahora has guardado datos sueltos en variables: un nombre, un número, un booleano. Pero en el mundo real trabajas con listas de cosas (productos de un carrito, episodios de una serie, usuarios de una app) y con entidades que tienen varias propiedades (un usuario con nombre, email y rol). Para eso existen los arrays y los objetos. En la sección de Lógica ya trabajaste con listas en pseudocódigo (buscar, filtrar, ordenar) — ahora vas a ver cómo JavaScript te da herramientas aún más potentes. Esta lección es probablemente la más importante de la sección de JavaScript.
Arrays: listas ordenadas
Un array es una lista ordenada de elementos. Cada elemento tiene un índice que empieza en 0:
// Crear un array
const generos = ["Acción", "Comedia", "Drama", "Sci-Fi", "Terror"];
// Acceder por índice (empiezan en 0)
console.log(generos[0]); // "Acción"
console.log(generos[3]); // "Sci-Fi"
// Longitud del array
console.log(generos.length); // 5
// Último elemento (truco clásico)
console.log(generos[generos.length - 1]); // "Terror"
// Último elemento (forma moderna)
console.log(generos.at(-1)); // "Terror"
console.log(generos.at(-2)); // "Sci-Fi"
Modificar arrays: push, pop, shift, unshift
const playlist = ["Bohemian Rhapsody", "Stairway to Heaven"];
// push: añadir al final
playlist.push("Hotel California");
console.log(playlist);
// ["Bohemian Rhapsody", "Stairway to Heaven", "Hotel California"]
// pop: quitar el último
const eliminada = playlist.pop();
console.log(eliminada); // "Hotel California"
console.log(playlist); // ["Bohemian Rhapsody", "Stairway to Heaven"]
// unshift: añadir al principio
playlist.unshift("Imagine");
console.log(playlist);
// ["Imagine", "Bohemian Rhapsody", "Stairway to Heaven"]
// shift: quitar el primero
playlist.shift();
console.log(playlist); // ["Bohemian Rhapsody", "Stairway to Heaven"]
// includes: comprobar si existe un elemento
console.log(playlist.includes("Imagine")); // false
console.log(playlist.includes("Bohemian Rhapsody")); // true
Recorrer arrays: forEach
forEach ejecuta una función por cada elemento del array. Ideal cuando quieres hacer algo con cada elemento sin necesitar un resultado nuevo:
const tareas = ["Comprar leche", "Estudiar JavaScript", "Llamar al dentista"];
tareas.forEach((tarea, indice) => {
console.log(`${indice + 1}. ${tarea}`);
});
// 1. Comprar leche
// 2. Estudiar JavaScript
// 3. Llamar al dentista
map: transformar cada elemento
map es como forEach, pero devuelve un nuevo array con los resultados. No modifica el original:
const precios = [10, 25, 50, 100];
// Aplicar un 21% de IVA a cada precio
const preciosConIVA = precios.map(precio => precio * 1.21);
console.log(preciosConIVA); // [12.1, 30.25, 60.5, 121]
// El array original no cambia
console.log(precios); // [10, 25, 50, 100]
// Otro ejemplo: formatear nombres
const nombres = ["ana garcía", "carlos lópez", "maría ruiz"];
const formateados = nombres.map(nombre => {
return nombre
.split(" ")
.map(palabra => palabra.charAt(0).toUpperCase() + palabra.slice(1))
.join(" ");
});
console.log(formateados); // ["Ana García", "Carlos López", "María Ruiz"]
filter: quedarte solo con lo que necesitas
const productos = [
{ nombre: "Camiseta", precio: 25, enStock: true },
{ nombre: "Pantalón", precio: 45, enStock: false },
{ nombre: "Zapatillas", precio: 89, enStock: true },
{ nombre: "Gorra", precio: 15, enStock: true },
{ nombre: "Chaqueta", precio: 120, enStock: false },
];
// Productos disponibles
const disponibles = productos.filter(producto => producto.enStock);
console.log(disponibles.length); // 3
// Productos baratos (menos de 50€)
const baratos = productos.filter(producto => producto.precio < 50);
console.log(baratos);
// [{ nombre: "Camiseta"... }, { nombre: "Pantalón"... }, { nombre: "Gorra"... }]
// Combinar: disponibles Y baratos
const oferta = productos.filter(p => p.enStock && p.precio < 50);
console.log(oferta);
// [{ nombre: "Camiseta"... }, { nombre: "Gorra"... }]
find y some/every
const usuarios = [
{ id: 1, nombre: "Ana", activo: true },
{ id: 2, nombre: "Luis", activo: false },
{ id: 3, nombre: "Sara", activo: true },
];
// find: devuelve el PRIMER elemento que cumple la condición
const ana = usuarios.find(u => u.nombre === "Ana");
console.log(ana); // { id: 1, nombre: "Ana", activo: true }
// findIndex: igual, pero devuelve el índice
const indiceLuis = usuarios.findIndex(u => u.nombre === "Luis");
console.log(indiceLuis); // 1
// some: ¿alguno cumple la condición? (devuelve true/false)
const hayInactivos = usuarios.some(u => !u.activo);
console.log(hayInactivos); // true
// every: ¿TODOS cumplen la condición?
const todosActivos = usuarios.every(u => u.activo);
console.log(todosActivos); // false
reduce: el método más potente
reduce recorre el array acumulando un resultado. Es perfecto para calcular totales, agrupar datos o transformar un array en cualquier otra estructura:
const carrito = [
{ nombre: "Teclado mecánico", precio: 89, cantidad: 1 },
{ nombre: "Ratón gaming", precio: 45, cantidad: 1 },
{ nombre: "Monitor 27\"", precio: 299, cantidad: 2 },
{ nombre: "Cable USB-C", precio: 12, cantidad: 3 },
];
// Calcular el total del carrito
const total = carrito.reduce((acumulador, producto) => {
return acumulador + (producto.precio * producto.cantidad);
}, 0); // 0 es el valor inicial del acumulador
console.log(total); // 534
// Contar el total de artículos
const totalArticulos = carrito.reduce((acc, producto) => {
return acc + producto.cantidad;
}, 0);
console.log(totalArticulos); // 7
// Resumen con reduce + template literal
const resumen = carrito.reduce((acc, p) => {
return acc + ` - ${p.nombre} x${p.cantidad}: ${p.precio * p.cantidad}€\n`;
}, "🛒 Tu carrito:\n");
console.log(resumen);
// 🛒 Tu carrito:
// - Teclado mecánico x1: 89€
// - Ratón gaming x1: 45€
// - Monitor 27" x2: 598€
// - Cable USB-C x3: 36€
Objetos: entidades con propiedades
Un objeto agrupa datos relacionados bajo un mismo nombre. Cada dato tiene una clave (key) y un valor:
const pelicula = {
titulo: "Interstellar",
director: "Christopher Nolan",
año: 2014,
generos: ["Sci-Fi", "Drama", "Aventura"],
puntuacion: 8.7,
enCartelera: false,
};
// Acceso con punto (lo más común)
console.log(pelicula.titulo); // "Interstellar"
console.log(pelicula.generos[0]); // "Sci-Fi"
// Acceso con corchetes (necesario cuando la clave es dinámica o tiene caracteres especiales)
console.log(pelicula["director"]); // "Christopher Nolan"
const campo = "puntuacion";
console.log(pelicula[campo]); // 8.7 — útil cuando no sabes la clave de antemano
// Modificar propiedades
pelicula.puntuacion = 8.8;
// Añadir propiedades nuevas
pelicula.duracion = 169;
// Eliminar propiedades
delete pelicula.enCartelera;
Objetos anidados
const usuario = {
nombre: "Elena",
perfil: {
avatar: "elena.jpg",
bio: "Desarrolladora frontend",
redes: {
github: "elena-dev",
linkedin: "elena-developer",
},
},
cursos: ["JavaScript", "React", "Node.js"],
};
// Acceder a datos anidados
console.log(usuario.perfil.redes.github); // "elena-dev"
console.log(usuario.cursos[1]); // "React"
// Optional chaining (?.) — evita errores si una propiedad no existe
console.log(usuario.perfil.redes.twitter); // undefined
console.log(usuario.trabajo?.empresa); // undefined (sin error)
// Sin ?. esto daría error: Cannot read property "empresa" of undefined
Spread operator: copiar y combinar
El spread ... "desparrama" los elementos de un array u objeto. Es imprescindible para trabajar de forma inmutable (sin modificar los originales):
// ARRAYS: copiar y combinar
const frontend = ["HTML", "CSS", "JavaScript"];
const backend = ["Node.js", "Python", "PHP"];
// Combinar arrays
const fullstack = [...frontend, ...backend];
console.log(fullstack);
// ["HTML", "CSS", "JavaScript", "Node.js", "Python", "PHP"]
// Copiar un array (copia superficial)
const copiaFrontend = [...frontend];
copiaFrontend.push("TypeScript");
console.log(frontend); // ["HTML", "CSS", "JavaScript"] — no se modifica
console.log(copiaFrontend); // ["HTML", "CSS", "JavaScript", "TypeScript"]
// OBJETOS: copiar y combinar
const configBase = { tema: "oscuro", idioma: "es", fontSize: 16 };
const configUsuario = { fontSize: 18, sidebar: true };
// Combinar objetos (las últimas propiedades ganan)
const config = { ...configBase, ...configUsuario };
console.log(config);
// { tema: "oscuro", idioma: "es", fontSize: 18, sidebar: true }
// Copiar y modificar (patrón muy común)
const configNueva = { ...config, tema: "claro" };
console.log(configNueva.tema); // "claro"
console.log(config.tema); // "oscuro" — el original intacto
Destructuring: extraer valores rápido
// DESTRUCTURING DE OBJETOS
const serie = {
titulo: "Breaking Bad",
temporadas: 5,
nota: 9.5,
plataforma: "Netflix",
};
// En vez de: const titulo = serie.titulo; const nota = serie.nota;
const { titulo, nota, plataforma } = serie;
console.log(titulo); // "Breaking Bad"
console.log(nota); // 9.5
// Renombrar variables
const { titulo: nombreSerie, temporadas: numTemporadas } = serie;
console.log(nombreSerie); // "Breaking Bad"
console.log(numTemporadas); // 5
// Valores por defecto
const { genero = "Drama" } = serie;
console.log(genero); // "Drama" (no existía en el objeto)
// DESTRUCTURING DE ARRAYS
const coordenadas = [40.4168, -3.7038]; // Madrid
const [latitud, longitud] = coordenadas;
console.log(latitud); // 40.4168
console.log(longitud); // -3.7038
// Saltar elementos
const podio = ["Oro", "Plata", "Bronce"];
const [, plata] = podio;
console.log(plata); // "Plata"
// Destructuring en parámetros de función — MUY útil
const mostrarPelicula = ({ titulo, director, año }) => {
console.log(`${titulo} (${año}) — Dir: ${director}`);
};
mostrarPelicula({ titulo: "Dune", director: "Denis Villeneuve", año: 2021 });
// "Dune (2021) — Dir: Denis Villeneuve"
structuredClone: copia profunda de verdad
El spread ... solo hace una copia superficial: si el objeto tiene objetos anidados, esos siguen apuntando al mismo sitio. structuredClone() resuelve eso de forma limpia:
const original = {
nombre: "Equipo A",
miembros: ["Ana", "Luis", "Sara"],
config: { tema: "oscuro", notificaciones: true },
};
// Copia superficial con spread — PROBLEMA
const copiaSuperficial = { ...original };
copiaSuperficial.miembros.push("Pedro");
console.log(original.miembros); // ["Ana", "Luis", "Sara", "Pedro"] — ¡modificaste el original!
// Copia profunda con structuredClone — SOLUCIÓN
const copiaProfunda = structuredClone(original);
copiaProfunda.miembros.push("María");
copiaProfunda.config.tema = "claro";
console.log(original.miembros); // ["Ana", "Luis", "Sara", "Pedro"] — intacto
console.log(original.config.tema); // "oscuro" — intacto
// Antes se usaba JSON.parse(JSON.stringify(obj)), pero eso falla con
// Dates, Maps, Sets, undefined, funciones... structuredClone es la forma correcta.
Object.groupBy: agrupar de forma nativa
Object.groupBy() es una adición moderna a JavaScript que te permite agrupar elementos de un array por una clave. Antes tenías que hacerlo manualmente con reduce:
const pedidos = [
{ id: 1, producto: "Camiseta", estado: "enviado" },
{ id: 2, producto: "Pantalón", estado: "pendiente" },
{ id: 3, producto: "Zapatillas", estado: "enviado" },
{ id: 4, producto: "Gorra", estado: "entregado" },
{ id: 5, producto: "Chaqueta", estado: "pendiente" },
{ id: 6, producto: "Bufanda", estado: "entregado" },
];
// Agrupar por estado
const porEstado = Object.groupBy(pedidos, pedido => pedido.estado);
console.log(porEstado);
// {
// enviado: [{ id: 1, ... }, { id: 3, ... }],
// pendiente: [{ id: 2, ... }, { id: 5, ... }],
// entregado: [{ id: 4, ... }, { id: 6, ... }],
// }
console.log(porEstado.pendiente.length); // 2
// Otro ejemplo: agrupar por rango de precio
const productos = [
{ nombre: "Cable", precio: 8 },
{ nombre: "Funda", precio: 15 },
{ nombre: "Teclado", precio: 89 },
{ nombre: "Monitor", precio: 299 },
{ nombre: "Ratón", precio: 45 },
];
const porRango = Object.groupBy(productos, p => {
if (p.precio < 20) return "económico";
if (p.precio < 100) return "medio";
return "premium";
});
console.log(porRango.económico); // [{ nombre: "Cable"... }, { nombre: "Funda"... }]
console.log(porRango.premium); // [{ nombre: "Monitor"... }]
Ejemplo completo: tracker de series
Vamos a combinar todo lo aprendido en un ejemplo real. Un tracker donde gestionas las series que estás viendo:
const series = [
{ titulo: "Breaking Bad", temporada: 5, episodio: 16, viendo: false, nota: 9.5 },
{ titulo: "The Bear", temporada: 2, episodio: 5, viendo: true, nota: 8.7 },
{ titulo: "Severance", temporada: 2, episodio: 3, viendo: true, nota: 8.9 },
{ titulo: "The Last of Us", temporada: 1, episodio: 9, viendo: false, nota: 8.8 },
{ titulo: "Succession", temporada: 4, episodio: 10, viendo: false, nota: 8.9 },
{ titulo: "Shogun", temporada: 1, episodio: 7, viendo: true, nota: 8.6 },
];
// ¿Qué estoy viendo ahora?
const enProgreso = series.filter(s => s.viendo);
console.log(`Viendo ${enProgreso.length} series`);
// Títulos de las que estoy viendo
const titulosActivos = enProgreso.map(s => s.titulo);
console.log(titulosActivos); // ["The Bear", "Severance", "Shogun"]
// ¿Alguna tiene nota mayor a 9?
const hayObrasMaestras = series.some(s => s.nota >= 9);
console.log(hayObrasMaestras); // true (Breaking Bad)
// Nota media de todas las series
const notaMedia = series.reduce((acc, s) => acc + s.nota, 0) / series.length;
console.log(`Nota media: ${notaMedia.toFixed(1)}`); // "Nota media: 8.9"
// Añadir una serie nueva (sin mutar el original)
const seriesActualizadas = [
...series,
{ titulo: "Fallout", temporada: 1, episodio: 1, viendo: true, nota: null },
];
// Marcar una serie como terminada (sin mutar)
const terminarSerie = (lista, titulo) => {
return lista.map(s =>
s.titulo === titulo ? { ...s, viendo: false } : s
);
};
const despuesDeShogun = terminarSerie(seriesActualizadas, "Shogun");
console.log(despuesDeShogun.find(s => s.titulo === "Shogun").viendo); // false
// Agrupar por estado
const porEstado = Object.groupBy(seriesActualizadas, s =>
s.viendo ? "En progreso" : "Completadas"
);
console.log(porEstado["En progreso"].length); // 4
console.log(porEstado["Completadas"].length); // 3
Ejemplo completo: operaciones de carrito de compra
// Estado inicial del carrito
let carrito = [
{ id: 1, nombre: "Teclado mecánico", precio: 89, cantidad: 1 },
{ id: 2, nombre: "Monitor 27\"", precio: 299, cantidad: 1 },
];
// Añadir producto (si ya existe, incrementar cantidad)
const añadirProducto = (carrito, producto) => {
const existente = carrito.find(item => item.id === producto.id);
if (existente) {
return carrito.map(item =>
item.id === producto.id
? { ...item, cantidad: item.cantidad + 1 }
: item
);
}
return [...carrito, { ...producto, cantidad: 1 }];
};
// Eliminar producto
const eliminarProducto = (carrito, idProducto) => {
return carrito.filter(item => item.id !== idProducto);
};
// Calcular total
const calcularTotal = (carrito) => {
return carrito.reduce((total, item) => total + item.precio * item.cantidad, 0);
};
// Número total de artículos
const contarArticulos = (carrito) => {
return carrito.reduce((total, item) => total + item.cantidad, 0);
};
// Usar las funciones
carrito = añadirProducto(carrito, { id: 3, nombre: "Ratón gaming", precio: 45 });
carrito = añadirProducto(carrito, { id: 1, nombre: "Teclado mecánico", precio: 89 }); // +1
console.log(carrito);
// [
// { id: 1, nombre: "Teclado mecánico", precio: 89, cantidad: 2 },
// { id: 2, nombre: "Monitor 27\"", precio: 299, cantidad: 1 },
// { id: 3, nombre: "Ratón gaming", precio: 45, cantidad: 1 },
// ]
console.log(`Total: ${calcularTotal(carrito)}€`); // "Total: 522€"
console.log(`Artículos: ${contarArticulos(carrito)}`); // "Artículos: 4"
carrito = eliminarProducto(carrito, 2);
console.log(`Total sin monitor: ${calcularTotal(carrito)}€`); // "Total sin monitor: 223€"
Gestiona tu playlist de música
Crea un archivo playlist.js con un array de al menos 8 canciones. Cada canción es un objeto con: titulo, artista, genero, duracionSegundos y favorita (boolean).
- Usa
filterpara obtener solo las canciones favoritas. - Usa
mappara crear un array con el formato"Artista - Título". - Usa
findpara buscar una canción por título. - Usa
reducepara calcular la duración total de la playlist en minutos. - Usa
somepara comprobar si hay alguna canción de un género concreto. - Usa
everypara comprobar si todas las canciones duran menos de 5 minutos. - Usa
Object.groupBypara agrupar las canciones por género. - Añade una canción nueva con spread (
...) sin mutar el array original. - Usa destructuring para extraer el título y artista de la primera canción.
lightbulb Pistas
Para la duración total: const totalSegundos = canciones.reduce((acc, c) => acc + c.duracionSegundos, 0), luego divide entre 60 y usa toFixed(1). Para Object.groupBy: Object.groupBy(canciones, c => c.genero). Para destructuring del primer elemento: const [{ titulo, artista }] = canciones;.