ES2023 y ES2024 llegaron con un conjunto de métodos de array que facilitan dos operaciones habituales: trabajar de forma inmutable sin mutar el original, y buscar o agrupar elementos. Métodos como toSorted, toReversed, with o Object.groupBy se han convertido rápidamente en herramientas cotidianas en código moderno.
toSorted y toReversed: variantes inmutables
El clásico Array.prototype.sort muta el array original, lo que causa bugs cuando el mismo array se reutiliza en varios sitios. toSorted devuelve un nuevo array ordenado sin tocar el original:
const precios = [34.99, 12.50, 89.00, 5.99]; // sort muta el original const ordenado1 = precios.sort((a, b) => a - b); console.log(precios === ordenado1); // true mismo array mutado // toSorted devuelve uno nuevo const precios2 = [34.99, 12.50, 89.00, 5.99]; const ordenado2 = precios2.toSorted((a, b) => a - b); console.log(precios2); // [34.99, 12.50, 89.00, 5.99] sin cambios console.log(ordenado2); // [5.99, 12.50, 34.99, 89.00]
Lo mismo aplica a toReversed:
const pasos = ['inicio', 'validar', 'guardar', 'notificar']; const inverso = pasos.toReversed(); console.log(pasos); // ['inicio', 'validar', 'guardar', 'notificar'] console.log(inverso); // ['notificar', 'guardar', 'validar', 'inicio']
toSpliced: eliminar o insertar sin mutar
toSpliced es la versión inmutable de splice. Devuelve un nuevo array con los cambios aplicados:
const tareas = ['diseño', 'desarrollo', 'testing', 'despliegue']; // Eliminar "testing" y añadir "QA" y "integración" const actualizado = tareas.toSpliced(2, 1, 'QA', 'integración'); console.log(tareas); // ['diseño', 'desarrollo', 'testing', 'despliegue'] console.log(actualizado); // ['diseño', 'desarrollo', 'QA', 'integración', 'despliegue'] // Útil en actualizaciones de estado en React: // setTareas(prev => prev.toSpliced(indice, 1));
with: cambiar un elemento por índice
with(indice, valor) devuelve una copia del array con un único elemento cambiado. Es la alternativa inmutable a la asignación por índice:
const colores = ['rojo', 'verde', 'azul']; const actualizado = colores.with(1, 'amarillo'); console.log(colores); // ['rojo', 'verde', 'azul'] console.log(actualizado); // ['rojo', 'amarillo', 'azul'] // Funciona con índices negativos const ultimo = colores.with(-1, 'naranja'); console.log(ultimo); // ['rojo', 'verde', 'naranja']
findLast y findLastIndex
Buscan desde el final del array hacia el principio. Útil cuando el elemento que interesa es el más reciente en una lista cronológica:
const eventos = [
{ tipo: 'click', ts: 1000 },
{ tipo: 'scroll', ts: 1200 },
{ tipo: 'click', ts: 1500 },
{ tipo: 'keydown', ts: 1800 },
{ tipo: 'click', ts: 2100 },
];
const ultimoClick = eventos.findLast(e => e.tipo === 'click');
console.log(ultimoClick); // { tipo: 'click', ts: 2100 }
const posicion = eventos.findLastIndex(e => e.tipo === 'click');
console.log(posicion); // 4
at(-1): acceso por índice negativo
El método at(), disponible desde ES2022, permite acceder a elementos por índice negativo de forma nativa sin el hack arr[arr.length - 1]:
const historial = ['pag1', 'pag2', 'pag3', 'pag4']; console.log(historial.at(-1)); // 'pag4' console.log(historial.at(-2)); // 'pag3' console.log(historial.at(0)); // 'pag1' // También funciona en strings y TypedArrays const str = 'javascript'; console.log(str.at(-1)); // 't'
Object.groupBy (ES2024)
Object.groupBy agrupa los elementos de un iterable en un objeto según la clave que devuelva la función callback. Es el groupBy que siempre echó en falta en JavaScript y que antes requería Lodash o una implementación manual:
const productos = [
{ nombre: 'Teclado', categoria: 'Periféricos', precio: 45 },
{ nombre: 'Monitor', categoria: 'Pantallas', precio: 299 },
{ nombre: 'Ratón', categoria: 'Periféricos', precio: 29 },
{ nombre: 'Webcam', categoria: 'Periféricos', precio: 79 },
{ nombre: 'Altavoces', categoria: 'Audio', precio: 89 },
];
const porCategoria = Object.groupBy(productos, p => p.categoria);
console.log(porCategoria);
// {
// Periféricos: [{nombre: 'Teclado',...}, {nombre: 'Ratón',...}, {nombre: 'Webcam',...}],
// Pantallas: [{nombre: 'Monitor',...}],
// Audio: [{nombre: 'Altavoces',...}]
// }
// Agrupar por rango de precio
const porRango = Object.groupBy(productos, p =>
p.precio < 50 ? 'económico' : p.precio < 150 ? 'medio' : 'premium'
);
Map.groupBy (ES2024)
Si la clave de agrupación es un objeto (en lugar de un string o número), Map.groupBy evita la conversión a string que haría Object.groupBy:
const ECONOMICO = { label: 'económico', max: 50 };
const MEDIO = { label: 'medio', max: 150 };
const PREMIUM = { label: 'premium', max: Infinity };
const rangos = Map.groupBy(productos, p =>
p.precio < 50 ? ECONOMICO : p.precio < 150 ? MEDIO : PREMIUM
);
for (const [rango, items] of rangos) {
console.log(`${rango.label}: ${items.map(p => p.nombre).join(', ')}`);
}
Array.fromAsync (ES2024)
Array.fromAsync convierte un iterable asíncrono en un array, esperando cada elemento antes de continuar:
async function* generarPaginas(url) {
let pagina = 1;
while (pagina <= 3) {
const res = await fetch(`${url}?page=${pagina}`);
const datos = await res.json();
yield datos;
pagina++;
}
}
const todasLasPaginas = await Array.fromAsync(
generarPaginas('/api/productos')
);
console.log(todasLasPaginas.length); // 3
Estos métodos no son solo azúcar sintáctico: toSorted, toReversed, toSpliced y with son fundamentales en arquitecturas donde la inmutabilidad es un requisito (Redux, signals, Svelte), y Object.groupBy elimina una de las necesidades más frecuentes de Lodash en proyectos modernos.
