Métodos modernos de arrays en JavaScript que reemplazan patrones genéricos comunes
Aprenda cómo los métodos más recientes de arrays en JavaScript, como groupBy, toSorted, with y findLast, reemplazan a los soluciones engorrosas de reduce, spread y reverse.
Grupar datos sin una biblioteca: Object.groupBy() y Map.groupBy()
Durante años, para dividir un array de elementos en categorías según una propiedad común era necesario escribir el propio acumulador reduce() o utilizar lodash.groupBy solo para esa tarea.
La especificación ECMAScript ahora ofrece una función de agrupamiento nativa a través de Object.groupBy() y Map.groupBy().
Problema que resuelve
Cuando se obtiene una colección plana de registros desde una API —transacciones, tareas, entradas de usuarios, lo que sea— a menudo es necesario clasificarlos en grupos antes de mostrarlos o aplicar lógica empresarial sobre ellos.
Antes: implementarlo manualmente con reduce()
const inventory = [
{ name: 'Apples', type: 'fruit', quantity: 10 },
{ name: 'Bananas', type: 'fruit', quantity: 0 },
{ name: 'Carrots', type: 'vegetable', quantity: 14 },
{ name: 'Broccoli', type: 'vegetable', quantity: 5 }
];
const groupedByReduce = inventory.reduce((acc, item) => {
const key = item.type;
if (!acc[key]) {
acc[key] = [];
}
acc[key].push(item);
return acc;
}, {});
Después: una llamada clara y específica a Object.groupBy
const inventory = [
{ name: 'Apples', type: 'fruit', quantity: 10 },
{ name: 'Bananas', type: 'fruit', quantity: 0 },
{ name: 'Carrots', type: 'vegetable', quantity: 14 },
{ name: 'Broccoli', type: 'vegetable', quantity: 5 }
];
const grouped = Object.groupBy(inventory, item => item.type);
/*
Output:
{
fruit: [
{ name: 'Apples', type: 'fruit', quantity: 10 },
{ name: 'Bananas', type: 'fruit', quantity: 0 }
],
vegetable: [
{ name: 'Carrots', type: 'vegetable', quantity: 14 },
{ name: 'Broccoli', type: 'vegetable', quantity: 5 }
]
}
*/
Utilizar Map.groupBy() en su lugar
Cuando la clave de agrupamiento no es una cadena o símbolo simple, sino un objeto más complejo o una referencia dinámica, Map.groupBy() es la opción más adecuada:
const vipTier = { tier: 'VIP' };
const standardTier = { tier: 'Standard' };
const customers = [
{ name: 'Alice', plan: vipTier },
{ name: 'Bob', plan: standardTier },
{ name: 'Charlie', plan: vipTier }
];
const groupedByPlan = Map.groupBy(customers, user => user.plan);
console.log(groupedByPlan.get(vipTier));
// Returns Alice and Charlie's records directly keyed by reference
Transformaciones inmutables en las que se puede confiar: toSorted(), toReversed(), toSpliced()
Un problema clásico de JavaScript es que los métodos familiares .sort(), .reverse() y .splice() reescriben el array en el mismo lugar en lugar de devolver uno nuevo.
En frameworks basados en un estado predecible — React, Redux, Vue y similares — modificar directamente un array tiende a generar errores sutiles y un rendering que silenciosamente deja de estar sincronizado.
Problema que resuelve
Para evitar estas trampas, los desarrolladores solían escribir primero copias defensivas, como [...array].sort() o array.slice().reverse(). La nueva familia de métodos “cambiar el array mediante copia” elimina por completo ese código genérico.
Comparación de los dos enfoques
// --- The Old Mutating Way ---
const scores = [88, 92, 79, 95];
const sortedMutated = scores.sort((a, b) => a - b);
console.log(scores); // [79, 88, 92, 95] -> Original data was destroyed!
// --- The Modern Non-Mutating Way ---
const originalScores = [88, 92, 79, 95];
const cleanSorted = originalScores.toSorted((a, b) => a - b);
console.log(originalScores); // [88, 92, 79, 95] -> Unchanged
console.log(cleanSorted); // [79, 88, 92, 95] -> Fresh array
Sustituir splice() por toSpliced()
toSpliced() permite eliminar, insertar o reemplazar elementos en cualquier posición sin alterar el array original:
const tabs = ['Home', 'About', 'Pricing', 'Contact'];
// Remove 'Pricing' (index 2) and insert 'Services' & 'Blog'
const updatedTabs = tabs.toSpliced(2, 1, 'Services', 'Blog');
console.log(tabs); // ['Home', 'About', 'Pricing', 'Contact']
console.log(updatedTabs); // ['Home', 'About', 'Services', 'Blog', 'Contact']
Actualizar un elemento de forma inmutable: with()
Supongamos que deseas cambiar el valor en un índice específico sin tocar el array de origen. El patrón habitual consiste en expandir el array en una copia, sobrescribir el índice y devolver el resultado. with() agrega todo esto en una sola expresión clara.
Problema que soluciona
Las actualizaciones de estado suelen requerir reemplazar un único elemento por posición manteniendo el arreglo circundante inmutable.
Comparación de los dos enfoques
const months = ['Jan', 'Mar', 'Mar', 'Apr'];
// Old way: Spread and mutate copy
const fixedMonthsOld = [...months];
fixedMonthsOld[1] = 'Feb';
// Modern way: array.with(index, value)
const fixedMonthsNew = months.with(1, 'Feb');
console.log(months); // ['Jan', 'Mar', 'Mar', 'Apr']
console.log(fixedMonthsNew); // ['Jan', 'Feb', 'Mar', 'Apr']
with() también admite índices negativos, por lo que reemplazar un elemento contado desde el final es igual de sencillo:
const tags = ['v1.0.0', 'v1.1.0', 'v1.2.0-beta'];
const stableTags = tags.with(-1, 'v1.2.0');
console.log(stableTags); // ['v1.0.0', 'v1.1.0', 'v1.2.0']
Búsqueda hacia atrás de forma eficiente: findLast() y findLastIndex()
Localizar un elemento con .find() funciona bien al escanear desde el principio, pero siempre comienza en el índice 0. Cuando lo que realmente se necesita es la última coincidencia, la solución habitual era invertir el arreglo primero; un enfoque que desperdicia tiempo y corre el riesgo de modificar datos que no se pretendía tocar.
Problema que soluciona
Los datos como los registros de logs, el historial de deshacer, las actualizaciones de actividad y los registros de transacciones están naturalmente ordenados por tiempo, y a menudo se desea obtener la entrada más reciente que cumpla con cierta condición.
Comparación de los dos enfoques
const logs = [
{ id: 1, action: 'LOGIN', timestamp: 1000 },
{ id: 2, action: 'UPLOAD', timestamp: 1010 },
{ id: 3, action: 'ERROR', timestamp: 1020 },
{ id: 4, action: 'UPLOAD', timestamp: 1030 },
{ id: 5, action: 'LOGOUT', timestamp: 1040 }
];
// Old way: Reversing creates overhead and array allocation
const lastUploadOld = [...logs].reverse().find(log => log.action === 'UPLOAD');
// Modern way: findLast iterates backwards directly
const lastUpload = logs.findLast(log => log.action === 'UPLOAD');
const lastUploadIdx = logs.findLastIndex(log => log.action === 'UPLOAD');
console.log(lastUpload); // { id: 4, action: 'UPLOAD', timestamp: 1030 }
console.log(lastUploadIdx); // 3
Dado que este método escanea desde el final y se detiene en cuanto encuentra una coincidencia, funciona en O(k) tiempo, donde k es la cantidad de pasos necesarios para llegar a la coincidencia; no hay necesidad de copiar o invertir todo el array primero.
5. Usar Map y Filter juntos: flatMap()
Es común encadenar .map() con .filter() o .flat() cuando cada elemento de origen puede generar cero, uno o varios resultados. flatMap() reduce esa cadena a una sola pasada.
Qué soluciona: evita la creación de arrays intermedios temporales cuando es necesario darle una nueva forma a datos anidados o filtrar y transformar en el mismo paso.
Ejemplo: aplanar una lista anidada de relaciones
const authors = [
{ name: 'Author A', books: ['Book 1', 'Book 2'] },
{ name: 'Author B', books: ['Book 3'] },
{ name: 'Author C', books: [] }
];
// Map + Flat produces 2 array allocations
const booksNested = authors.map(a => a.books).flat();
// flatMap runs the map and flattens depth-1 in one pass
const allBooks = authors.flatMap(a => a.books);
console.log(allBooks); // ['Book 1', 'Book 2', 'Book 3']
Ejemplo: transformar y filtrar al mismo tiempo
Puedes descartar un elemento devolviendo un array vacío y conservarlo devolviendo un array con un único elemento:
const rawInputs = ['42', 'invalid', '100', 'undefined', '256'];
const validNumbers = rawInputs.flatMap(input => {
const num = parseInt(input, 10);
return Number.isNaN(num) ? [] : [num];
});
console.log(validNumbers); // [42, 100, 256]
6. Acceso más limpio a elementos: at()
Obtener el último elemento de un array solía significar escribir array[array.length - 1], lo cual es incómodo y fácil de error.
at() toma prestada la convención de índices negativos familiar de Python, permitiéndote contar hacia atrás desde el final tanto de arrays como de cadenas.
Antes y después
const queue = ['Alice', 'Bob', 'Charlie', 'Dana'];
// Old way
const lastPersonOld = queue[queue.length - 1];
const secondLastOld = queue[queue.length - 2];
// Modern way
const lastPerson = queue.at(-1); // 'Dana'
const secondLast = queue.at(-2); // 'Charlie'
El resultado es más corto, menos propenso a errores de tipo “off-by-one” en las expresiones calculadas, y funciona bien con llamadas encadenadas a métodos ya que no es necesario almacenar el array en una variable temporal primero.
Cuándo utilizarlo
Lista de verificación para modernización
- Revisar cómo se gestiona el estado: sustituir
.sort()y.reverse()por.toSorted()y.toReversed()para evitar errores de mutación silenciosa. - Reducir el código genérico: utilizar
Object.groupBy()en lugar de objetos acumuladores creados manualmente. - Acelerar las búsquedas: reemplazar los patrones de buscar primero y luego invertir el orden por
findLast()para datos ordenados por tiempo.
.at(-1) y .with(index, value) en lugar de realizar cálculos manuales basados en la longitud y copias defensivas de arrays.Lecturas relacionadas
- Nueve patrones comunes que desencadenan re-renderizaciones innecesarias en React — Explica nueve patrones cotidianos relacionados con el estado y los efectos en React que amplían silenciosamente el alcance de las re-renderizaciones, y cómo reestructurar los componentes para mantener las actualizaciones localizadas.
- Diez errores ocultos en componentes React que ralentizan las aplicaciones modernas — Conozca diez errores comunes en componentes React, desde deficiencias en el HTML semántico hasta la falta de memorización, y las soluciones necesarias para mantener las aplicaciones rápidas, accesibles y libres de errores en 2026.