Saltar al contenido
metodos-de-arrays.js · devschool

Métodos de arrays en JavaScript

Lección 15 de 38 · 11 min de lectura · Actualizado el

En esta lección
  1. Cómo funciona el callback
  2. forEach: hacer algo con cada elemento
  3. map: transformar cada elemento
  4. filter: quedarte con algunos
  5. reduce: de muchos a uno
  6. Buscar: find, findIndex y findLast
  7. Comprobar: some, every e includes
  8. Ordenar con sort y un comparador
  9. Versiones que no modifican: toSorted, toReversed y with
  10. Aplanar: flat y flatMap
  11. Crear arrays con Array.from
  12. Encadenar métodos
  13. Mutar o no mutar
  14. Errores frecuentes
  15. 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], porque map pasa también la posición y parseInt la toma como base numérica. Usa map(Number) o map((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:

VueltaaccnDevuelve
1077
27411
311920
420525
525328

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 reduce te cuesta leerlo, un for...of con 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 a va antes,
  • positivo si b va 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 originalDevuelven algo nuevo sin tocarlo
push, pop, shift, unshiftmap, filter, slice, concat
splicetoSpliced
sorttoSorted
reversetoReversed
arr[i] = xwith(i, x)
fill, copyWithinflat, 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 map para recorrer sin transformar: si no usas el array que devuelve, lo correcto es forEach o for...of.
  • Olvidar el return en un callback con llaves: map devuelve [undefined, ...] y filter devuelve [].
  • reduce sin valor inicial: error con arrays vacíos y resultados raros con objetos.
  • sort() sin comparador con números: ordena como texto.
  • Esperar que find devuelva un array: devuelve un elemento o undefined.
  • Ordenar el array original sin querer: usa toSorted.

Resumen

MétodoDevuelvePara qué
forEachundefinedHacer algo con cada elemento
mapArray del mismo tamañoTransformar
filterArray con los que cumplenSeleccionar
reduceUn valorSumar, contar, agrupar
find / findLastUn elemento o undefinedBuscar uno
findIndexPosición o -1Buscar dónde está
some / everytrue / false¿Alguno? ¿Todos?
includestrue / false¿Está este valor?
sort / toSortedArray ordenadoOrdenar con comparador
flat / flatMapArray planoAplanar
Array.fromArray nuevoConvertir o generar

Pruébalo tú

Cambia el código y pulsa Ejecutar (o Ctrl + Enter).

metodos-de-arrays.js
Consola

Pon a prueba lo que has aprendido

[JavaScript] ¿Qué muestra la consola?
const precios = [10, 20, 30];
const dobles = precios.map((p) => { p * 2 });
console.log(dobles);

[JavaScript] ¿Qué muestra la consola?
const total = [1, 2, 3, 4]
  .filter((n) => n % 2 === 0)
  .map((n) => n * 10)
  .reduce((acc, n) => acc + n, 0);
console.log(total);

[JavaScript] Necesitas el usuario cuyo id es 7 dentro de un array de usuarios. ¿Qué método es el adecuado?

[JavaScript] ¿Qué muestra la consola?
const notas = [3, 1, 2];
const ordenadas = notas.toSorted((a, b) => b - a);
console.log(notas, ordenadas);

¿Te ha quedado claro? Márcala y verás tu progreso en el explorador.