Métodos de arrays en JavaScript
En esta lección
- Cómo funciona el callback
- forEach: hacer algo con cada elemento
- map: transformar cada elemento
- filter: quedarte con algunos
- reduce: de muchos a uno
- Buscar: find, findIndex y findLast
- Comprobar: some, every e includes
- Ordenar con sort y un comparador
- Versiones que no modifican: toSorted, toReversed y with
- Aplanar: flat y flatMap
- Crear arrays con Array.from
- Encadenar métodos
- Mutar o no mutar
- Errores frecuentes
- Resumen
En la lección de arrays viste cómo crear listas, añadir y quitar elementos y una primera pasada por map, filter y reduce. Aquí vas a dominar esos métodos a fondo, junto con el resto de métodos que reciben una función. Con ellos sustituyes la mayoría de bucles for por código más corto, más legible y con menos errores. Todos son funciones de orden superior: si no tienes claro qué es un callback, repasa callbacks y funciones flecha.
En toda la lección usaremos este inventario de una tienda:
const productos = [
{ nombre: "Teclado", precio: 45, categoria: "informatica", stock: 12 },
{ nombre: "Ratón", precio: 20, categoria: "informatica", stock: 0 },
{ nombre: "Taza", precio: 8, categoria: "hogar", stock: 30 },
{ nombre: "Lámpara", precio: 35, categoria: "hogar", stock: 4 },
{ nombre: "Auriculares", precio: 60, categoria: "informatica", stock: 7 },
];
Cómo funciona el callback
Todos estos métodos llaman a tu función una vez por cada elemento y le pasan tres argumentos: el elemento, su posición y el array completo. Normalmente solo usarás el primero, a veces el segundo:
productos.forEach((producto, indice) => {
console.log(`${indice + 1}. ${producto.nombre}`);
});
// 1. Teclado
// 2. Ratón
// 3. Taza
// 4. Lámpara
// 5. Auriculares
Lo que cambia entre métodos es qué hacen con lo que devuelve tu función. Esa es la clave para elegir uno u otro.
forEach: hacer algo con cada elemento
forEach recorre el array y no devuelve nada (undefined). Úsalo para efectos: mostrar en consola, pintar en la página, enviar datos.
const r = [1, 2, 3].forEach((n) => n * 2);
console.log(r); // undefined
forEach no se puede cortar con break. Si necesitas parar a mitad, usa for...of o un método como find o some, que se detienen solos.
map: transformar cada elemento
map crea un array nuevo del mismo tamaño con lo que devuelve tu función para cada elemento:
const nombres = productos.map((p) => p.nombre);
// [ 'Teclado', 'Ratón', 'Taza', 'Lámpara', 'Auriculares' ]
const conIva = productos.map((p) => ({
nombre: p.nombre,
precioConIva: +(p.precio * 1.21).toFixed(2),
}));
// [ { nombre: 'Teclado', precioConIva: 54.45 }, { nombre: 'Ratón', precioConIva: 24.2 }, ... ]
Si olvidas el return (o los paréntesis alrededor del objeto), obtendrás un array lleno de undefined.
Cuidado:
["1", "2", "3"].map(parseInt)da[1, NaN, NaN], porquemappasa también la posición yparseIntla toma como base numérica. Usamap(Number)omap((t) => parseInt(t)).
filter: quedarte con algunos
filter crea un array nuevo solo con los elementos para los que tu función devuelve true:
const enStock = productos.filter((p) => p.stock > 0);
enStock.length; // 4
const hogar = productos.filter((p) => p.categoria === "hogar");
// Taza y Lámpara
Si ninguno cumple la condición, devuelve un array vacío [], nunca null.
reduce: de muchos a uno
reduce es el más potente y el que más cuesta al principio. Recorre el array llevando un acumulador: tu función recibe el acumulador y el elemento actual, y devuelve el nuevo acumulador. El segundo argumento de reduce es el valor inicial.
Sumar
const notas = [7, 4, 9, 5, 3];
const suma = notas.reduce((acc, n) => acc + n, 0); // 28
Así va cambiando el acumulador en cada vuelta:
| Vuelta | acc | n | Devuelve |
|---|---|---|---|
| 1 | 0 | 7 | 7 |
| 2 | 7 | 4 | 11 |
| 3 | 11 | 9 | 20 |
| 4 | 20 | 5 | 25 |
| 5 | 25 | 3 | 28 |
Con objetos es igual. El valor total del almacén:
const valor = productos.reduce((total, p) => total + p.precio * p.stock, 0); // 1340
El valor inicial importa
Si no lo pones, reduce usa el primer elemento como acumulador. Funciona para sumar números, pero falla con un array vacío y con arrays de objetos:
[].reduce((a, b) => a + b);
// TypeError: Reduce of empty array with no initial value
Pon siempre el valor inicial: 0 para sumar, {} para agrupar, [] para construir una lista.
Contar
El acumulador puede ser un objeto. Contar votos:
const votos = ["si", "no", "si", "abstencion", "si", "no"];
const recuento = votos.reduce((cuenta, voto) => {
cuenta[voto] = (cuenta[voto] ?? 0) + 1;
return cuenta;
}, {});
// { si: 3, no: 2, abstencion: 1 }
Agrupar
const porCategoria = productos.reduce((grupos, p) => {
(grupos[p.categoria] ??= []).push(p.nombre);
return grupos;
}, {});
// { informatica: [ 'Teclado', 'Ratón', 'Auriculares' ], hogar: [ 'Taza', 'Lámpara' ] }
??= asigna un array vacío solo si esa categoría aún no existe. Para agrupar hay además un atajo moderno, Object.groupBy, que verás en Set y Map.
Consejo: si un
reducete cuesta leerlo, unfor...ofcon una variable acumuladora es perfectamente válido. La legibilidad manda.
Buscar: find, findIndex y findLast
find devuelve el primer elemento que cumple la condición, o undefined si ninguno la cumple. Se detiene en cuanto lo encuentra:
productos.find((p) => p.precio > 40); // { nombre: 'Teclado', ... }
productos.find((p) => p.precio > 100); // undefined
findIndex devuelve la posición, o -1:
productos.findIndex((p) => p.nombre === "Taza"); // 2
productos.findIndex((p) => p.nombre === "Silla"); // -1
findLast y findLastIndex hacen lo mismo empezando por el final:
productos.findLast((p) => p.categoria === "hogar").nombre; // "Lámpara"
¿find o filter? find cuando buscas uno (un usuario por su id); filter cuando quieres todos los que cumplen.
Comprobar: some, every e includes
Devuelven true o false:
productos.some((p) => p.stock === 0); // true: al menos uno agotado
productos.every((p) => p.precio > 5); // true: todos cuestan más de 5
some para en cuanto encuentra uno que cumple; every, en cuanto encuentra uno que no cumple. Curiosidad: [].every(...) es true, porque ningún elemento incumple la condición.
Para buscar un valor concreto (sin función), includes es más directo que some, y además detecta NaN, cosa que indexOf no hace:
[1, 2, NaN].includes(NaN); // true
[1, 2, NaN].indexOf(NaN); // -1
Ordenar con sort y un comparador
sort recibe una función que compara dos elementos a y b y devuelve:
- un número negativo si
ava antes, - positivo si
bva antes, - 0 si da igual.
Números
const nums = [10, 1, 5, 100];
nums.sort(); // [ 1, 10, 100, 5 ] ordena como texto (!)
nums.sort((a, b) => a - b); // [ 1, 5, 10, 100 ] ascendente
nums.sort((a, b) => b - a); // [ 100, 10, 5, 1 ] descendente
Textos con localeCompare
const nombres = ["Óscar", "ana", "Zoe", "Ángel", "ñandú", "nuria"];
nombres.sort();
// [ 'Zoe', 'ana', 'nuria', 'Ángel', 'Óscar', 'ñandú' ] mal
nombres.sort((a, b) => a.localeCompare(b, "es"));
// [ 'ana', 'Ángel', 'nuria', 'ñandú', 'Óscar', 'Zoe' ] bien
Objetos
Compara la propiedad que te interese. Y si hay empate, encadena un segundo criterio con ||:
// Por precio, de menor a mayor
productos.sort((a, b) => a.precio - b.precio);
// Por categoría y, dentro de cada una, del más caro al más barato
productos.sort(
(a, b) => a.categoria.localeCompare(b.categoria) || b.precio - a.precio
);
// Lámpara, Taza, Auriculares, Teclado, Ratón
Si la primera comparación da 0 (misma categoría), || pasa a la segunda.
Versiones que no modifican: toSorted, toReversed y with
sort y reverse cambian el array original, lo que puede romper otras partes del programa que lo usan. Desde ES2023 tienes versiones que devuelven una copia:
const original = [3, 1, 2];
const ordenado = original.toSorted(); // [ 1, 2, 3 ]
const alReves = original.toReversed(); // [ 2, 1, 3 ]
const cambiado = original.with(0, 99); // [ 99, 1, 2 ]
console.log(original); // [ 3, 1, 2 ] intacto
with(indice, valor) es la versión sin mutación de array[indice] = valor. También existe toSpliced, la versión sin mutación de splice.
Aplanar: flat y flatMap
flat convierte arrays anidados en uno plano. Por defecto aplana un nivel:
[1, [2, 3], [4, [5, 6]]].flat(); // [ 1, 2, 3, 4, [ 5, 6 ] ]
[1, [2, 3], [4, [5, 6]]].flat(Infinity); // [ 1, 2, 3, 4, 5, 6 ]
flatMap es un map seguido de flat(). Muy útil cuando cada elemento produce una lista:
const pedidos = [
{ id: 1, articulos: ["libro", "taza"] },
{ id: 2, articulos: ["lápiz"] },
];
pedidos.map((p) => p.articulos); // [ [ 'libro', 'taza' ], [ 'lápiz' ] ]
pedidos.flatMap((p) => p.articulos); // [ 'libro', 'taza', 'lápiz' ]
Crear arrays con Array.from
Array.from convierte en array cualquier cosa recorrible (un texto, un Set, una NodeList del DOM) y admite una función para transformar:
Array.from("hola"); // [ 'h', 'o', 'l', 'a' ]
Array.from({ length: 5 }, (_, i) => i * 2); // [ 0, 2, 4, 6, 8 ]
Array.from(document.querySelectorAll("li")); // array con los <li> de la página
El truco { length: 5 } sirve para generar arrays de cualquier tamaño. El _ es un nombre para el parámetro que no usas.
Encadenar métodos
Como map, filter, toSorted y slice devuelven arrays, puedes encadenarlos. Cada línea es un paso y se lee de arriba abajo:
const masCaros = productos
.filter((p) => p.stock > 0) // solo los que hay
.toSorted((a, b) => b.precio - a.precio) // del más caro al más barato
.slice(0, 3) // los tres primeros
.map((p) => `${p.nombre}: ${p.precio} €`); // a texto
// [ 'Auriculares: 60 €', 'Teclado: 45 €', 'Lámpara: 35 €' ]
Pon un método por línea: es más fácil de leer y de depurar, porque puedes comentar un paso para ver el resultado intermedio.
Mutar o no mutar
Saber qué métodos cambian el array original te ahorrará muchos errores:
| Modifican el original | Devuelven algo nuevo sin tocarlo |
|---|---|
push, pop, shift, unshift | map, filter, slice, concat |
splice | toSpliced |
sort | toSorted |
reverse | toReversed |
arr[i] = x | with(i, x) |
fill, copyWithin | flat, flatMap, Array.from |
Cuidado con sort: además de modificar el original, devuelve el mismo array, no una copia:
const orden = [3, 1, 2];
const copia = orden.sort();
console.log(copia === orden, orden); // true [ 1, 2, 3 ]
Errores frecuentes
- Usar
mappara recorrer sin transformar: si no usas el array que devuelve, lo correcto esforEachofor...of. - Olvidar el
returnen un callback con llaves:mapdevuelve[undefined, ...]yfilterdevuelve[]. reducesin valor inicial: error con arrays vacíos y resultados raros con objetos.sort()sin comparador con números: ordena como texto.- Esperar que
finddevuelva un array: devuelve un elemento oundefined. - Ordenar el array original sin querer: usa
toSorted.
Resumen
| Método | Devuelve | Para qué |
|---|---|---|
forEach | undefined | Hacer algo con cada elemento |
map | Array del mismo tamaño | Transformar |
filter | Array con los que cumplen | Seleccionar |
reduce | Un valor | Sumar, contar, agrupar |
find / findLast | Un elemento o undefined | Buscar uno |
findIndex | Posición o -1 | Buscar dónde está |
some / every | true / false | ¿Alguno? ¿Todos? |
includes | true / false | ¿Está este valor? |
sort / toSorted | Array ordenado | Ordenar con comparador |
flat / flatMap | Array plano | Aplanar |
Array.from | Array nuevo | Convertir o generar |
Pruébalo tú
Cambia el código y pulsa Ejecutar (o Ctrl + Enter).
Pon a prueba lo que has aprendido
¿Te ha quedado claro? Márcala y verás tu progreso en el explorador.