Módulo 06: Arrays
Objetivos del módulo
- Comprender qué es un array y cuándo usarlo
- Crear arrays de distintas formas: literal,
Array.from,Array.ofyfill - Acceder a sus elementos con índices y con
at(), y manejarlength - Añadir, quitar, buscar y recorrer elementos con los métodos adecuados
- Transformar datos con
map,filter,reduce,flatyflatMap - Ordenar correctamente (números, textos con acentos y objetos)
- Distinguir los métodos que modifican el array de los que devuelven uno nuevo (
toSorted,toReversed,toSpliced,with) - Usar la desestructuración, el operador spread y la agrupación con
Object.groupBy - Entender la diferencia entre copia superficial y copia profunda
- Encadenar métodos para resolver problemas reales con código legible
1. ¿Qué es un array?
📘 Concepto: Un array (también llamado arreglo o vector) es una lista ordenada de valores. Cada valor ocupa una posición numerada llamada índice, que empieza en 0.
Piensa en un array como en una estantería con huecos numerados: en el hueco 0 pones un libro, en el 1 otro, y así sucesivamente. Si quieres el tercer libro, no lo buscas por el título: vas directamente al hueco número 2.
// En lugar de alumno1, alumno2, alumno3... una sola variable para toda la lista
const alumnos = ['Ana', 'Luis', 'Marta'];
console.log(alumnos[0]); // Ana
console.log(alumnos[2]); // Marta
console.log(alumnos.length); // 3
Características de los arrays en JavaScript:
| Característica | Explicación |
|---|---|
| Ordenados | Los elementos mantienen el orden en que los colocas |
| Indexados desde 0 | El primer elemento es [0] y el último [length - 1] |
| Dinámicos | Crecen y encogen sin declarar un tamaño previo |
| Heterogéneos | Pueden mezclar tipos ([1, 'hola', true]), aunque no es buena práctica |
| Son objetos | typeof [] devuelve 'object'; para comprobar si algo es un array usa Array.isArray(valor) |
💡 Consejo: Aunque JavaScript lo permite, procura que todos los elementos de un array sean del mismo tipo (todo números, todo textos o todo objetos con la misma forma). Así el código es más predecible y los métodos como
sortomapse comportan como esperas.
📘 Concepto: En este módulo los comentarios muestran los resultados de forma compacta, como
[1, 2, 3]. Node.js los escribe con espacios ([ 1, 2, 3 ]) y el navegador añade la longitud ((3) [1, 2, 3]), pero el valor es el mismo.
2. Crear arrays
2.1. Literal de array (la forma habitual)
const vacio = [];
const numeros = [10, 20, 30];
const frutas = ['manzana', 'pera', 'uva'];
const mezcla = [1, 'dos', { tres: 3 }, [4]];
const colores = ['rojo', 'verde', 'azul',]; // la coma final está permitida
console.log(colores.length); // 3
2.2. El constructor Array y su trampa
const a = new Array(1, 2, 3); // [1, 2, 3]
const b = new Array(3); // ¡No es [3]! Es un array con 3 huecos vacíos
console.log(b); // [ <3 empty items> ]
console.log(b.length); // 3
⚠️ Importante:
new Array(n)con un único número crea un array de longitudnlleno de huecos (empty slots), no un array con ese número. Es una fuente clásica de errores. Usa literales oArray.of.
2.3. Array.of
Crea un array con los argumentos que le pases, sin la trampa anterior:
console.log(Array.of(3)); // [3]
console.log(Array.of(1, 2, 3)); // [1, 2, 3]
2.4. Array.from: crear arrays a partir de otras cosas
Array.from convierte en array cualquier iterable (cadenas, Set, Map…) o cualquier objeto “parecido a un array” (con length). Además acepta una función de mapeo como segundo argumento.
// Desde una cadena: un elemento por carácter
console.log(Array.from('hola')); // ['h', 'o', 'l', 'a']
// Desde un Set (elimina duplicados de paso)
console.log(Array.from(new Set([1, 1, 2, 3, 3]))); // [1, 2, 3]
// Desde un objeto con length: generar secuencias
console.log(Array.from({ length: 5 }, (_, i) => i)); // [0, 1, 2, 3, 4]
console.log(Array.from({ length: 5 }, (_, i) => i + 1)); // [1, 2, 3, 4, 5]
💡 Consejo: El patrón
Array.from({ length: n }, (_, i) => ...)es la forma idiomática de generar arrays de un tamaño concreto. El guion bajo_indica que ignoramos el primer parámetro (el valor, que esundefined).
2.5. fill: rellenar con un valor
const ceros = new Array(5).fill(0);
console.log(ceros); // [0, 0, 0, 0, 0]
console.log([1, 2, 3, 4, 5].fill(9, 1, 3)); // [1, 9, 9, 4, 5] (fill(valor, inicio, fin) sin incluir fin)
⚠️ Importante:
fillcoloca el mismo valor en todas las posiciones. Si ese valor es un objeto o un array, todas las posiciones apuntan al mismo objeto:
const malo = new Array(3).fill([]);
malo[0].push('x');
console.log(malo); // [['x'], ['x'], ['x']] ← ¡los tres son el mismo array!
const bueno = Array.from({ length: 3 }, () => []);
bueno[0].push('x');
console.log(bueno); // [['x'], [], []]
3. Acceder a los elementos
3.1. Índices y at()
const letras = ['a', 'b', 'c', 'd', 'e'];
console.log(letras[0]); // a
console.log(letras[letras.length - 1]); // e (último, forma clásica)
console.log(letras[10]); // undefined (no existe, no da error)
// at() admite índices negativos: -1 es el último, -2 el penúltimo...
console.log(letras.at(0)); // a
console.log(letras.at(-1)); // e
console.log(letras.at(-2)); // d
📘 Concepto:
at(indice)(ES2022) hace lo mismo que los corchetes pero admite índices negativos que cuentan desde el final. Con corchetes,letras[-1]devuelveundefined, porque busca una propiedad llamada"-1".
3.2. Modificar por índice
const notas = [5, 7, 9];
notas[1] = 8;
console.log(notas); // [5, 8, 9]
// Asignar más allá del final crea huecos (evítalo)
notas[5] = 10;
console.log(notas); // [5, 8, 9, <2 empty items>, 10]
console.log(notas.length); // 6
3.3. La propiedad length
length siempre vale el índice más alto + 1. Además se puede escribir: si la reduces, recortas el array.
const lista = [1, 2, 3, 4, 5];
lista.length = 2;
console.log(lista); // [1, 2]
lista.length = 0; // forma rápida de vaciar un array (lo modifica)
console.log(lista); // []
⚠️ Importante:
constimpide reasignar la variable, no modificar el contenido. Conconst lista = [...]puedes hacerlista.push(6)olista.length = 0, pero nolista = [].
4. Añadir y quitar elementos
| Método | Qué hace | Devuelve | ¿Modifica el array? |
|---|---|---|---|
push(...elementos) | Añade al final | Nueva longitud | Sí |
pop() | Quita el último | El elemento quitado | Sí |
unshift(...elementos) | Añade al principio | Nueva longitud | Sí |
shift() | Quita el primero | El elemento quitado | Sí |
splice(inicio, cuantos, ...nuevos) | Quita y/o inserta en cualquier posición | Array con los quitados | Sí |
const cola = ['Ana', 'Luis'];
cola.push('Marta'); // al final
console.log(cola); // ['Ana', 'Luis', 'Marta']
cola.unshift('Pedro'); // al principio
console.log(cola); // ['Pedro', 'Ana', 'Luis', 'Marta']
const ultimo = cola.pop(); // quita el último
const primero = cola.shift(); // quita el primero
console.log(ultimo, primero); // Marta Pedro
console.log(cola); // ['Ana', 'Luis']
console.log(cola.push('Eva', 'Raúl')); // 4 (admite varios y devuelve la nueva longitud)
💡 Consejo: Truco para recordarlo:
push/poptrabajan por el final (como una pila de platos),shift/unshiftpor el principio.shiftyunshiftson más lentos en arrays enormes porque tienen que renumerar todos los elementos.
4.1. splice: la navaja suiza
splice(inicio, cuantosBorrar, ...elementosAInsertar) modifica el array en cualquier posición:
const meses = ['ene', 'feb', 'abr', 'may', 'jun'];
// Insertar 'mar' en la posición 2 sin borrar nada
meses.splice(2, 0, 'mar');
console.log(meses); // ['ene', 'feb', 'mar', 'abr', 'may', 'jun']
// Borrar 2 elementos desde la posición 4
const borrados = meses.splice(4, 2);
console.log(borrados); // ['may', 'jun']
console.log(meses); // ['ene', 'feb', 'mar', 'abr']
// Reemplazar: borrar 1 en la posición 0 e insertar otro
meses.splice(0, 1, 'enero');
console.log(meses); // ['enero', 'feb', 'mar', 'abr']
✏️ Ejercicio: Partiendo de
const dias = ['lun', 'mar', 'jue', 'vie'], usa un únicosplicepara insertar'mié'en su sitio. Después, con otrosplice, sustituye'vie'por'viernes'.
4.2. Eliminar un elemento concreto
const tareas = ['comprar', 'estudiar', 'correr'];
const posicion = tareas.indexOf('estudiar');
if (posicion !== -1) {
tareas.splice(posicion, 1);
}
console.log(tareas); // ['comprar', 'correr']
// Alternativa sin modificar el original: tareas.filter((t) => t !== 'estudiar') (sección 7)
⚠️ Importante: No uses
delete tareas[1]: deja un hueco y no cambialength. Para quitar elementos usaspliceofilter.
5. Buscar en un array
5.1. Por valor: indexOf, lastIndexOf e includes
const numeros = [3, 7, 3, 9, NaN];
console.log(numeros.indexOf(3)); // 0 (primera aparición)
console.log(numeros.lastIndexOf(3)); // 2 (última aparición)
console.log(numeros.indexOf(100)); // -1 (no está)
console.log(numeros.includes(9)); // true
// Diferencia sutil con NaN
console.log(numeros.indexOf(NaN)); // -1 (usa ===, y NaN !== NaN)
console.log(numeros.includes(NaN)); // true (usa SameValueZero)
💡 Consejo: Si solo quieres saber si está, usa
includes: se lee mejor queindexOf(x) !== -1y funciona conNaN.
5.2. Por condición: find, findIndex, findLast y findLastIndex
Cuando buscas “el primer elemento que cumpla algo”, pasas una función (un callback) que devuelve true o false:
const productos = [
{ id: 1, nombre: 'Teclado', precio: 25 },
{ id: 2, nombre: 'Ratón', precio: 15 },
{ id: 3, nombre: 'Monitor', precio: 180 },
{ id: 4, nombre: 'Alfombrilla', precio: 8 },
];
const caro = productos.find((p) => p.precio > 100);
console.log(caro); // { id: 3, nombre: 'Monitor', precio: 180 }
const indiceRaton = productos.findIndex((p) => p.nombre === 'Ratón');
console.log(indiceRaton); // 1
// Desde el final (ES2023)
const ultimoBarato = productos.findLast((p) => p.precio < 20);
console.log(ultimoBarato.nombre); // Alfombrilla
console.log(productos.findLastIndex((p) => p.precio < 20)); // 3
console.log(productos.find((p) => p.precio > 1000)); // undefined (no hay ninguno)
5.3. Preguntas de sí o no: some y every
const edades = [17, 22, 35, 41];
console.log(edades.some((e) => e < 18)); // true (¿alguno es menor?)
console.log(edades.every((e) => e >= 18)); // false (¿todos son mayores?)
console.log([].every((e) => e > 0)); // true (en un array vacío nadie incumple)
5.4. ¿Qué método de búsqueda uso?
| Quiero… | Método | Devuelve si no hay |
|---|---|---|
| Saber si un valor está | includes(valor) | false |
| La posición de un valor | indexOf(valor) / lastIndexOf(valor) | -1 |
| El primer elemento que cumple una condición | find(fn) | undefined |
| La posición del primero que cumple | findIndex(fn) | -1 |
| El último que cumple / su posición | findLast(fn) / findLastIndex(fn) | undefined / -1 |
| Saber si alguno cumple | some(fn) | false |
| Saber si todos cumplen | every(fn) | true (array vacío) |
| Todos los que cumplen | filter(fn) (sección 7) | [] |
💡 Consejo:
find,findIndex,someyeveryse detienen en cuanto conocen la respuesta.filtersiempre recorre el array entero. Si solo necesitas uno, usafind.
6. Recorrer un array
const ciudades = ['Madrid', 'Sevilla', 'Bilbao'];
// 1) for...of: el más legible cuando solo necesitas el valor
for (const ciudad of ciudades) {
console.log(ciudad);
}
// 2) for...of con entries(): valor e índice a la vez
for (const [indice, ciudad] of ciudades.entries()) {
console.log(`${indice + 1}. ${ciudad}`);
}
// 1. Madrid
// 2. Sevilla
// 3. Bilbao
// 3) forEach: ejecuta una función por elemento
ciudades.forEach((ciudad, indice) => {
console.log(`${indice}: ${ciudad}`);
});
| Forma | ¿Admite break/continue? | ¿Funciona con await dentro? | Cuándo usarla |
|---|---|---|---|
for (let i...) | Sí | Sí | Necesitas el índice, saltos o recorrer al revés |
for...of | Sí | Sí | Opción por defecto, la más legible |
forEach | No | No espera (cada vuelta lanza su promesa) | Efectos sencillos encadenados tras otros métodos |
for...in | Sí | Sí | No para arrays: recorre claves (como texto) y propiedades heredadas |
⚠️ Importante:
forEachno se puede interrumpir conbreaky siempre devuelveundefined. Si necesitas parar al encontrar algo, usafor...ofconbreak, o directamentefind/some.
⚠️ Importante: No uses
for...incon arrays. Está pensado para las claves de un objeto (módulo 07), devuelve los índices como cadenas ('0','1'…) y puede incluir propiedades añadidas al prototipo.
Además de entries(), existen keys() (solo índices) y values() (solo valores). Los tres devuelven iteradores, que estudiarás a fondo en el módulo 11.
7. Transformar: map, filter y reduce
Estos tres métodos son el corazón del JavaScript moderno. Ninguno modifica el array original: devuelven un valor nuevo.
flowchart LR
A["[1, 2, 3, 4]"] -->|"map(n => n * 10)"| B["[10, 20, 30, 40]"]
A -->|"filter(n => n % 2 === 0)"| C["[2, 4]"]
A -->|"reduce((acc, n) => acc + n, 0)"| D["10"]
7.1. map: transformar cada elemento
map aplica una función a cada elemento y devuelve un array nuevo del mismo tamaño con los resultados.
const precios = [10, 25, 40];
const conIva = precios.map((precio) => precio * 1.21);
console.log(conIva); // [12.1, 30.25, 48.4]
console.log(precios); // [10, 25, 40] (el original no cambia)
const usuarios = [
{ nombre: 'Ana', edad: 28 },
{ nombre: 'Luis', edad: 34 },
];
const nombres = usuarios.map((u) => u.nombre);
console.log(nombres); // ['Ana', 'Luis']
// El callback recibe (elemento, índice, array)
console.log(nombres.map((nombre, i) => `${i + 1}-${nombre}`)); // ['1-Ana', '2-Luis']
⚠️ Importante: Si en una arrow function con llaves olvidas el
return,mapdevuelveundefineden cada posición. Y para devolver un objeto sin llaves, envuélvelo en paréntesis:
const mal = [1, 2].map((n) => { n * 2; }); // [undefined, undefined]
const bien = [1, 2].map((n) => n * 2); // [2, 4]
const objetos = [1, 2].map((n) => ({ valor: n })); // [{ valor: 1 }, { valor: 2 }]
console.log(mal, bien, objetos);
7.2. filter: quedarse con los que cumplen
const notas = [4.5, 7, 3, 9.5, 6, 5];
const aprobadas = notas.filter((nota) => nota >= 5);
console.log(aprobadas); // [7, 9.5, 6, 5]
// Truco: eliminar valores "falsy" (0, '', null, undefined, NaN, false)
const sucios = ['a', '', null, 'b', undefined, 0, 'c'];
console.log(sucios.filter(Boolean)); // ['a', 'b', 'c']
7.3. reduce: reducir a un único valor
reduce recorre el array llevando un acumulador. En cada vuelta, el callback recibe el acumulador y el elemento actual, y devuelve el nuevo acumulador.
📘 Concepto: Imagina una hucha: empiezas con 0 € (el valor inicial) y, por cada moneda (elemento), la hucha pasa a valer lo que tenía más la moneda. Al final,
reducete devuelve la hucha.
const numeros = [5, 10, 15];
const suma = numeros.reduce((acumulado, n) => acumulado + n, 0);
console.log(suma); // 30
// Paso a paso:
// vuelta 1: acumulado = 0, n = 5 → 5
// vuelta 2: acumulado = 5, n = 10 → 15
// vuelta 3: acumulado = 15, n = 15 → 30
// Contar ocurrencias: el acumulador puede ser un objeto
const votos = ['sí', 'no', 'sí', 'sí', 'abstención', 'no'];
const recuento = votos.reduce((acc, voto) => {
acc[voto] = (acc[voto] ?? 0) + 1;
return acc;
}, {});
console.log(recuento); // { 'sí': 3, no: 2, 'abstención': 1 }
⚠️ Importante: Pasa siempre el valor inicial (el segundo argumento). Sin él,
reduceusa el primer elemento como acumulador y, si el array está vacío, lanza un error:
console.log([].reduce((a, b) => a + b, 0)); // 0
try {
[].reduce((a, b) => a + b);
} catch (error) {
console.log(error.message); // Reduce of empty array with no initial value
}
reduceRight hace lo mismo pero recorriendo de derecha a izquierda: ['c', 'b', 'a'].reduceRight((acc, l) => acc + l, '') devuelve 'abc'.
💡 Consejo:
reducees muy potente, pero no lo uses para todo. Si unmap, unfiltero un buclefor...ofexpresan la idea con más claridad, prefiérelos. El código se lee muchas más veces de las que se escribe.
7.4. flat y flatMap: aplanar
const anidado = [1, [2, 3], [4, [5, 6]]];
console.log(anidado.flat()); // [1, 2, 3, 4, [5, 6]] (profundidad 1)
console.log(anidado.flat(2)); // [1, 2, 3, 4, 5, 6]
console.log(anidado.flat(Infinity)); // [1, 2, 3, 4, 5, 6] (todos los niveles)
// flatMap = map + flat(1): útil cuando cada elemento produce varios
const frases = ['hola mundo', 'buenos días'];
console.log(frases.map((f) => f.split(' '))); // [['hola', 'mundo'], ['buenos', 'días']]
console.log(frases.flatMap((f) => f.split(' '))); // ['hola', 'mundo', 'buenos', 'días']
// flatMap también permite filtrar: devolver [] descarta el elemento
console.log([1, -2, 3].flatMap((n) => (n > 0 ? [n] : []))); // [1, 3]
8. Ordenar arrays
8.1. El peligro de sort() sin argumentos
⚠️ Importante: Por defecto,
sort()convierte los elementos a texto y los ordena alfabéticamente por sus códigos Unicode. Con números, el resultado es sorprendente:
const numeros = [10, 1, 5, 100, 25];
numeros.sort();
console.log(numeros); // [1, 10, 100, 25, 5] ← ¡'100' va antes que '25' como texto!
8.2. La función comparadora
Para ordenar bien, pasa una función (a, b) que devuelva:
| Valor devuelto | Significado |
|---|---|
| Negativo | a va antes que b |
| Positivo | a va después que b |
0 | Mantienen su orden relativo (el ordenamiento es estable desde ES2019) |
const numeros = [10, 1, 5, 100, 25];
numeros.sort((a, b) => a - b); // ascendente
console.log(numeros); // [1, 5, 10, 25, 100]
numeros.sort((a, b) => b - a); // descendente
console.log(numeros); // [100, 25, 10, 5, 1]
8.3. Ordenar textos con acentos: localeCompare
const palabras = ['árbol', 'zapato', 'avión', 'Ébano', 'abeja'];
console.log([...palabras].sort());
// ['abeja', 'avión', 'zapato', 'Ébano', 'árbol'] ← los acentos van al final
console.log([...palabras].sort((a, b) => a.localeCompare(b, 'es')));
// ['abeja', 'árbol', 'avión', 'Ébano', 'zapato'] ← orden correcto en español
localeCompare compara según las reglas del idioma. Para ordenar listas grandes, Intl.Collator es más eficiente y permite opciones, como ordenar números dentro del texto de forma natural:
const ficheros = ['tema10.pdf', 'tema2.pdf', 'tema1.pdf'];
const collator = new Intl.Collator('es', { numeric: true });
console.log(ficheros.sort(collator.compare)); // ['tema1.pdf', 'tema2.pdf', 'tema10.pdf']
8.4. Ordenar objetos
const alumnos = [
{ nombre: 'Lucía', nota: 8.5 },
{ nombre: 'Álvaro', nota: 6 },
{ nombre: 'Marcos', nota: 8.5 },
{ nombre: 'Elena', nota: 9.2 },
];
// Por nota descendente y, a igual nota, por nombre
alumnos.sort((a, b) => b.nota - a.nota || a.nombre.localeCompare(b.nombre, 'es'));
console.log(alumnos.map((a) => `${a.nombre} (${a.nota})`));
// ['Elena (9.2)', 'Lucía (8.5)', 'Marcos (8.5)', 'Álvaro (6)']
💡 Consejo: El truco
criterio1 || criterio2funciona porque, si el primer criterio da0(empate),||pasa al segundo. Para invertir el orden actual de un array, sin ordenar, existereverse().
⚠️ Importante:
sortyreversemodifican el array original y además lo devuelven. Esto confunde:const ordenado = lista.sort()ordena tambiénlista. La solución está en la sección siguiente.
9. Métodos inmutables de ES2023
📘 Concepto: Un método mutable cambia el array sobre el que lo llamas. Un método inmutable deja el original intacto y devuelve una copia con el cambio. Las bibliotecas como React o el código que comparte datos entre partes de la aplicación agradecen mucho la inmutabilidad: si nadie modifica los datos “por detrás”, los errores son más fáciles de encontrar.
ES2023 añadió versiones inmutables de los métodos mutables más usados:
const original = [3, 1, 2];
const ordenado = original.toSorted((a, b) => a - b);
const invertido = original.toReversed();
const sinPrimero = original.toSpliced(0, 1); // como splice, pero devuelve la copia
const conCambio = original.with(1, 99); // como original[1] = 99, en copia
console.log(original); // [3, 1, 2] ← intacto
console.log(ordenado); // [1, 2, 3]
console.log(invertido); // [2, 1, 3]
console.log(sinPrimero); // [1, 2]
console.log(conCambio); // [3, 99, 2]
console.log(original.with(-1, 0)); // [3, 1, 0] (admite índices negativos)
// original.with(10, 0) lanza RangeError: with no admite índices fuera de rango
Tabla: métodos mutables frente a inmutables
| Mutable (modifica el original) | Inmutable (devuelve una copia) |
|---|---|
sort(fn) | toSorted(fn) |
reverse() | toReversed() |
splice(inicio, n, ...nuevos) | toSpliced(inicio, n, ...nuevos) |
arr[i] = valor | with(i, valor) |
push(x) | [...arr, x] o concat(x) |
unshift(x) | [x, ...arr] |
pop() / shift() | slice(0, -1) / slice(1) |
fill(valor) | Array.from(arr, () => valor) |
copyWithin(...) | (sin equivalente directo) |
Otros métodos que nunca modifican el array: map, filter, reduce, flat, flatMap, slice, concat, join, at, includes, find, some, every…
💡 Consejo: En código nuevo, prefiere
toSortedy compañía salvo que necesites modificar el array a propósito (por rendimiento con millones de elementos, por ejemplo).
10. slice, concat, spread y join
10.1. slice frente a splice
Sus nombres se parecen, pero son muy distintos:
slice(inicio, fin) | splice(inicio, cuantos, ...nuevos) | |
|---|---|---|
| ¿Modifica el original? | No | Sí |
| Devuelve | Un trozo (copia) | Los elementos eliminados |
| Segundo parámetro | Índice final (no incluido) | Cantidad a eliminar |
| Uso típico | Copiar o extraer un tramo | Insertar, borrar o reemplazar en el sitio |
const letras = ['a', 'b', 'c', 'd', 'e'];
console.log(letras.slice(1, 3)); // ['b', 'c'] (del 1 al 3, sin incluir el 3)
console.log(letras.slice(2)); // ['c', 'd', 'e']
console.log(letras.slice(-2)); // ['d', 'e'] (los dos últimos)
console.log(letras.slice()); // copia completa
console.log(letras); // ['a', 'b', 'c', 'd', 'e'] (intacto)
const quitados = letras.splice(1, 3);
console.log(quitados); // ['b', 'c', 'd']
console.log(letras); // ['a', 'e'] (¡modificado!)
💡 Consejo: Regla mnemotécnica: splice cambia el array; slice solo corta una loncha (en inglés, a slice) y deja el original igual.
10.2. Unir arrays: concat y el operador spread
const frutas = ['manzana', 'pera'];
const verduras = ['lechuga', 'tomate'];
const compra1 = frutas.concat(verduras, ['pan']);
const compra2 = [...frutas, ...verduras, 'pan']; // spread: más flexible
const compra3 = ['leche', ...frutas, 'huevos']; // insertar en medio
console.log(compra2); // ['manzana', 'pera', 'lechuga', 'tomate', 'pan']
console.log(compra3); // ['leche', 'manzana', 'pera', 'huevos']
// Spread también sirve para pasar un array como argumentos sueltos
const temperaturas = [18, 23, 15, 27];
console.log(Math.max(...temperaturas)); // 27
📘 Concepto: El operador spread (
...) “despliega” los elementos de un iterable. Ya lo viste con los parámetros rest en el módulo 05: la sintaxis es la misma, pero rest recoge elementos y spread los reparte.
10.3. join: de array a texto
const palabras = ['JavaScript', 'es', 'genial'];
console.log(palabras.join(' ')); // JavaScript es genial
console.log(palabras.join('-')); // JavaScript-es-genial
console.log(palabras.join()); // JavaScript,es,genial (coma por defecto)
// null y undefined se convierten en cadena vacía
console.log(['a', null, 'b', undefined].join('|')); // a||b|
// La operación inversa es split (módulo 08)
console.log('2026-10-03'.split('-')); // ['2026', '10', '03']
⚠️ Importante: No uses
+con arrays: los convierte a texto.[1, 2] + [3]da la cadena'1,23', no[1, 2, 3].
11. Desestructuración de arrays
📘 Concepto: La desestructuración extrae valores de un array y los guarda en variables en una sola línea, usando la posición.
const coordenadas = [40.41, -3.70, 650];
// Sin desestructurar
const latitud1 = coordenadas[0];
const longitud1 = coordenadas[1];
// Desestructurando
const [latitud, longitud, altitud] = coordenadas;
console.log(latitud, longitud, altitud); // 40.41 -3.7 650
11.1. Saltar elementos, valores por defecto y rest
const podio = ['Ana', 'Luis', 'Marta', 'Pedro', 'Eva'];
// Saltar posiciones dejando huecos entre comas
const [oro, , bronce] = podio;
console.log(oro, bronce); // Ana Marta
// Rest: recoger "el resto" en un array (debe ir al final)
const [ganador, ...demas] = podio;
console.log(ganador); // Ana
console.log(demas); // ['Luis', 'Marta', 'Pedro', 'Eva']
// Valores por defecto (se usan si el valor es undefined)
const [a = 1, b = 2, c = 3] = [10, undefined];
console.log(a, b, c); // 10 2 3
11.2. Intercambiar variables
let x = 1;
let y = 2;
[x, y] = [y, x];
console.log(x, y); // 2 1
11.3. En parámetros y con funciones que devuelven arrays
// Una función puede devolver varios valores en un array
function minMax(numeros) {
return [Math.min(...numeros), Math.max(...numeros)];
}
const [minimo, maximo] = minMax([4, 9, 1, 7]);
console.log(minimo, maximo); // 1 9
// Desestructurar en los parámetros del callback
console.log([[1, 2], [3, 4]].map(([px, py]) => px + py)); // [3, 7]
12. Agrupar: Object.groupBy y Map.groupBy
Agrupar elementos según un criterio es una tarea tan común que ES2024 incorporó dos funciones específicas. Antes había que hacerlo con reduce.
const inventario = [
{ nombre: 'espárragos', tipo: 'verdura', cantidad: 5 },
{ nombre: 'plátanos', tipo: 'fruta', cantidad: 0 },
{ nombre: 'cabra', tipo: 'carne', cantidad: 23 },
{ nombre: 'cerezas', tipo: 'fruta', cantidad: 5 },
{ nombre: 'pescado', tipo: 'carne', cantidad: 22 },
];
const porTipo = Object.groupBy(inventario, (producto) => producto.tipo);
console.log(Object.keys(porTipo)); // ['verdura', 'fruta', 'carne']
console.log(porTipo.fruta.map((p) => p.nombre)); // ['plátanos', 'cerezas']
⚠️ Importante:
Object.groupByes un método estático deObject(noinventario.groupBy(...)) y devuelve un objeto sin prototipo (Object.create(null)). Por eso Node lo muestra como[Object: null prototype] {...}y no tiene métodos comohasOwnProperty; usaObject.hasOwn(porTipo, 'fruta')si lo necesitas.
Map.groupBy hace lo mismo pero devuelve un Map (módulo 11), cuyas claves pueden ser de cualquier tipo, por ejemplo objetos:
const aprobados = { etiqueta: 'aprobados' };
const suspensos = { etiqueta: 'suspensos' };
const notas = [7, 3, 9, 4.5, 5];
const grupos = Map.groupBy(notas, (n) => (n >= 5 ? aprobados : suspensos));
console.log(grupos.get(aprobados)); // [7, 9, 5]
console.log(grupos.get(suspensos)); // [3, 4.5]
13. Array.fromAsync (ES2026)
Array.fromAsync es la versión asíncrona de Array.from: crea un array a partir de un iterable asíncrono o de un iterable de promesas, esperando a cada valor. Devuelve una promesa, por eso se usa con await.
const esperar = (ms, valor) => new Promise((resolver) => setTimeout(() => resolver(valor), ms));
const promesas = [esperar(30, 'a'), esperar(10, 'b'), esperar(20, 'c')];
const valores = await Array.fromAsync(promesas);
console.log(valores); // ['a', 'b', 'c'] (en el orden original)
📘 Concepto: No te preocupes si
Promise,asyncyawaitte resultan extraños: los estudiarás en el módulo 12 (Asincronía), donde volveremos aArray.fromAsynccon generadores asíncronos. Elawait“suelto” del ejemplo funciona porque los módulos ES admiten top-level await.
14. Arrays multidimensionales
Un array puede contener otros arrays. Así se representan tablas, tableros o matrices:
// Tablero de tres en raya: 3 filas × 3 columnas
const tablero = [
['X', 'O', 'X'],
['O', 'X', ' '],
[' ', ' ', 'O'],
];
console.log(tablero[0][2]); // X (fila 0, columna 2)
tablero[2][0] = 'X'; // colocar ficha
// Recorrer filas y columnas
for (const fila of tablero) {
console.log(fila.join(' | '));
}
// X | O | X
// O | X |
// X | | O
Crear una matriz de tamaño dado (recuerda la trampa de fill con objetos):
const filas = 3;
const columnas = 4;
const matriz = Array.from({ length: filas }, () => new Array(columnas).fill(0));
matriz[1][2] = 7;
console.log(matriz); // [[0, 0, 0, 0], [0, 0, 7, 0], [0, 0, 0, 0]]
// Transponer (filas ↔ columnas) con map
const datos = [[1, 2, 3], [4, 5, 6]];
const transpuesta = datos[0].map((_, col) => datos.map((fila) => fila[col]));
console.log(transpuesta); // [[1, 4], [2, 5], [3, 6]]
✏️ Ejercicio: Escribe una función que reciba una matriz numérica y devuelva un array con la suma de cada fila. Hazlo con
mapyreduce.
💡 Consejo: Para ver una matriz o un array de objetos de forma clara en el navegador o en Node, usa
console.table(matriz).
15. Copia superficial frente a copia profunda
📘 Concepto: Los arrays se guardan por referencia. Si haces
const b = a, no copias el array: tienes dos nombres para el mismo array, como dos llaves de la misma casa.
const a = [1, 2, 3];
const b = a; // misma referencia
b.push(4);
console.log(a); // [1, 2, 3, 4] ← ¡también cambió! (y a === b es true)
15.1. Copia superficial (shallow copy)
Crea un array nuevo, pero los elementos que sean objetos siguen compartidos:
const original = [1, 2, 3];
const copia1 = [...original]; // también: original.slice() o Array.from(original)
copia1.push(99);
console.log(original); // [1, 2, 3] (intacto)
// El problema aparece con objetos dentro
const usuarios = [{ nombre: 'Ana' }, { nombre: 'Luis' }];
const copiaSuperficial = [...usuarios];
copiaSuperficial[0].nombre = 'ANA';
console.log(usuarios[0].nombre); // ANA ← el objeto interior es el mismo
15.2. Copia profunda (deep copy) con structuredClone
const usuarios = [{ nombre: 'Ana', aficiones: ['leer'] }];
const copiaProfunda = structuredClone(usuarios);
copiaProfunda[0].nombre = 'ANA';
copiaProfunda[0].aficiones.push('nadar');
console.log(usuarios); // [{ nombre: 'Ana', aficiones: ['leer'] }]
console.log(copiaProfunda); // [{ nombre: 'ANA', aficiones: ['leer', 'nadar'] }]
| Técnica | Tipo de copia | Notas |
|---|---|---|
const b = a | Ninguna (misma referencia) | Solo otro nombre |
[...a], a.slice(), Array.from(a) | Superficial | Suficiente si los elementos son primitivos |
structuredClone(a) | Profunda | Copia objetos anidados, Date, Map, Set… No copia funciones |
JSON.parse(JSON.stringify(a)) | Profunda (limitada) | Truco antiguo: pierde undefined, fechas, Map… Evítalo |
💡 Consejo: Profundizaremos en referencias y
structuredCloneen el módulo 07 (Objetos).
16. Encadenamiento de métodos: ejemplos reales
Como map, filter, toSorted y compañía devuelven arrays, puedes encadenarlos formando una “tubería” de transformaciones. Escribe cada paso en su propia línea para que se lea de arriba abajo.
16.1. Carrito de la compra
const carrito = [
{ producto: 'Camiseta', precio: 19.99, unidades: 2, categoria: 'ropa' },
{ producto: 'Auriculares', precio: 59.9, unidades: 1, categoria: 'electrónica' },
{ producto: 'Calcetines', precio: 4.5, unidades: 0, categoria: 'ropa' },
{ producto: 'Cable USB-C', precio: 9.95, unidades: 3, categoria: 'electrónica' },
];
// 1) Líneas con unidades, 2) importe por línea, 3) ordenadas de mayor a menor
const lineas = carrito
.filter((item) => item.unidades > 0)
.map((item) => ({ ...item, importe: item.precio * item.unidades }))
.toSorted((a, b) => b.importe - a.importe);
for (const { producto, unidades, importe } of lineas) {
console.log(`${producto.padEnd(12)} x${unidades} ${importe.toFixed(2)} €`);
}
// Auriculares x1 59.90 €
// Camiseta x2 39.98 €
// Cable USB-C x3 29.85 €
const total = lineas.reduce((suma, linea) => suma + linea.importe, 0);
console.log(`Total: ${total.toFixed(2)} €`); // Total: 129.73 €
⚠️ Importante: Los números decimales en JavaScript tienen errores de redondeo (
0.1 + 0.2no es exactamente0.3). Por eso mostramos los importes contoFixed(2). En aplicaciones de facturación reales se trabaja en céntimos enteros o con bibliotecas decimales.
16.2. Notas de alumnos
const alumnos = [
{ nombre: 'Lucía', notas: [8, 9, 7.5] },
{ nombre: 'Álvaro', notas: [4, 5.5, 3] },
{ nombre: 'Marcos', notas: [6, 7, 6.5] },
{ nombre: 'Elena', notas: [9.5, 10, 9] },
{ nombre: 'Iker', notas: [] },
];
const media = (numeros) => numeros.reduce((s, n) => s + n, 0) / numeros.length;
const informe = alumnos
.filter(({ notas }) => notas.length > 0) // descartar sin notas
.map(({ nombre, notas }) => ({ nombre, media: media(notas) }))
.toSorted((a, b) => b.media - a.media)
.map(({ nombre, media: m }, i) => `${i + 1}. ${nombre}: ${m.toFixed(2)}${m >= 5 ? '' : ' (suspenso)'}`);
console.log(informe.join('\n'));
// 1. Elena: 9.50
// 2. Lucía: 8.17
// 3. Marcos: 6.50
// 4. Álvaro: 4.17 (suspenso)
console.log(Math.max(...alumnos.flatMap((a) => a.notas))); // 10 (nota más alta del grupo)
✏️ Ejercicio: Añade al informe de notas una línea final con el porcentaje de aprobados (media ≥ 5) usando
filterylength.
17. Rendimiento y legibilidad
- La legibilidad va primero. Una cadena
filter → map → reducecon nombres claros es más fácil de mantener que un bucle con cinco variables. Solo optimiza cuando midas un problema real. - Cada método encadenado recorre el array y crea uno nuevo. Con 100 elementos da igual; con millones de elementos en un bucle crítico, un único
for...oforeducepuede ser más eficiente. - Usa el método más específico:
finden lugar defilter(...)[0],someen lugar defilter(...).length > 0,includesen lugar deindexOf(...) !== -1. Expresan mejor la intención y se detienen antes. - Buscar en arrays grandes es lento (
includesyfindrecorren elemento a elemento). Si vas a consultar muchas veces “¿está este valor?”, convierte el array en unSet; si buscas por id, usa unMap(módulo 11). - No mutes los datos que recibes en una función: usa
toSorted,with, spread… Mutar datos compartidos es una de las fuentes de errores más difíciles de rastrear. - No dejes huecos (
new Array(n)sin rellenar,delete arr[i],arr[100] = x): los métodos tratan los huecos de forma inconsistente (forEachlos salta,for...oflos ve comoundefined).
💡 Consejo: Para medir de forma rápida, rodea el código con
console.time('etiqueta')yconsole.timeEnd('etiqueta'). Por ejemplo, 1000 llamadas aincludessobre un array de 100 000 números tardan cientos de veces más que 1000 llamadas ahassobre unSetcon los mismos datos.
18. Ejercicios
Ejercicio 1: Estadísticas básicas
Dado const temperaturas = [22, 25, 19, 30, 28, 17, 24], calcula y muestra: la temperatura máxima, la mínima, la media con un decimal y cuántos días superaron los 24 grados. Usa Math.max/Math.min con spread, reduce y filter.
Ejercicio 2: Gestor de lista de tareas
Crea un array tareas vacío y escribe las funciones anadir(texto), completar(indice) (marca la tarea como hecha), eliminar(indice) y listar(), que muestre algo como 1. [x] Comprar pan. Cada tarea es un objeto { texto, hecha }. Usa push, splice y entries().
Ejercicio 3: Ordenación múltiple sin mutar
Con el array de alumnos de la sección 8.4, obtén sin modificar el original (usa toSorted): (a) los alumnos ordenados alfabéticamente respetando los acentos; (b) ordenados por nota ascendente; (c) los tres mejores. Comprueba al final con console.log que el array original no ha cambiado.
Ejercicio 4: Eliminar duplicados y contar
Dado const visitas = ['inicio', 'blog', 'inicio', 'contacto', 'blog', 'inicio'], obtén: (a) la lista de páginas sin repetir (con Set y spread); (b) un objeto con el número de visitas por página usando reduce; (c) la página más visitada.
Ejercicio 5: Agrupar pedidos
Tienes un array de pedidos { id, cliente, total, estado } donde estado es 'pendiente', 'enviado' o 'entregado'. Crea al menos 8 pedidos de ejemplo y usa Object.groupBy para agruparlos por estado. Muestra, para cada estado, cuántos pedidos hay y la suma de sus totales.
Ejercicio 6: Matriz y tres en raya
Escribe una función hayGanador(tablero) que reciba un tablero 3×3 como el de la sección 14 y devuelva 'X', 'O' o null. Debe comprobar filas, columnas y las dos diagonales. Pista: genera un array con las 8 líneas posibles y usa find con every.
Ejercicio 7: Desestructuración
Escribe una función separar(numeros) que devuelva [pares, impares] y úsala con desestructuración. Después, a partir de const ranking = ['Ana', 'Luis', 'Marta', 'Pedro'], extrae en una sola línea el primero, el tercero y el resto en un array.
Ejercicio 8: Informe de ventas encadenado
Dado un array de ventas { vendedor, producto, unidades, precioUnitario, mes } con al menos 10 registros, obtén con métodos encadenados: el importe total de cada venta, las ventas de un mes concreto, el ranking de vendedores por facturación (de mayor a menor) y el producto más vendido en unidades. Presenta el resultado con console.table.
Resumen
| Concepto | Sintaxis | Modifica el original |
|---|---|---|
| Crear | [], Array.of(x), Array.from(iterable, fn), new Array(n).fill(v) | — |
| Acceder | arr[i], arr.at(-1), arr.length | — |
| Añadir/quitar al final | push(x), pop() | Sí |
| Añadir/quitar al principio | unshift(x), shift() | Sí |
| Insertar/borrar en medio | splice(i, n, ...nuevos) | Sí |
| Buscar valor | includes(v), indexOf(v) | No |
| Buscar por condición | find, findIndex, findLast, findLastIndex | No |
| Comprobar | some(fn), every(fn) | No |
| Recorrer | for...of, entries(), forEach(fn) | No |
| Transformar | map(fn), filter(fn), reduce(fn, inicial), flat(n), flatMap(fn) | No |
| Ordenar | sort((a, b) => a - b), localeCompare, reverse() | Sí |
| Inmutables (ES2023) | toSorted, toReversed, toSpliced, with | No |
| Extraer y unir | slice(i, f), concat, [...a, ...b], join(sep) | No |
| Desestructurar | const [a, , c = 0, ...resto] = arr | — |
| Agrupar (ES2024) | Object.groupBy(arr, fn), Map.groupBy(arr, fn) | No |
| Asíncrono (ES2026) | await Array.fromAsync(iterable) | — |
| Copiar | [...arr] (superficial), structuredClone(arr) (profunda) | — |