Ordenar y buscar parece lo más fácil del mundo, pero sort() tiene la sorpresa más famosa de JavaScript: ordena los números como si fueran texto. Aquí está cómo hacerlo bien en cada caso.
También te puede interesar
Números: siempre con comparador
const numeros = [10, 1, 5, 100, 25]; console.log([...numeros].sort()); console.log([...numeros].sort((a, b) => a - b)); console.log([...numeros].sort((a, b) => b - a));
salida[ 1, 10, 100, 25, 5 ] [ 1, 5, 10, 25, 100 ] [ 100, 25, 10, 5, 1 ]
Sin argumentos, sort() convierte cada elemento en texto y los compara letra a letra, como en un diccionario. El comparador (a, b) => a - b devuelve un número negativo si a va antes, positivo si va después y cero si dan igual. Para el orden inverso, b - a.
Texto en español: localeCompare
const nombres = ["zoe", "Álvaro", "ana", "Óscar", "Beatriz", "ñu", "nora"];
console.log([...nombres].sort().join(", "));
console.log([...nombres].sort((a, b) => a.localeCompare(b, "es")).join(", "));
const colador = new Intl.Collator("es", { sensitivity: "base" });
console.log([...nombres].sort(colador.compare).join(", "));
console.log(colador.compare("Ana", "ana"), colador.compare("canción", "cancion"));salidaBeatriz, ana, nora, zoe, Álvaro, Óscar, ñu Álvaro, ana, Beatriz, nora, ñu, Óscar, zoe Álvaro, ana, Beatriz, nora, ñu, Óscar, zoe 0 0
El sort() a secas ordena por el código de cada carácter: las mayúsculas van antes que las minúsculas, y las letras con acento y la ñ, al final de todo. localeCompare con "es" sigue las reglas del español. Si vas a ordenar muchos elementos, Intl.Collator hace lo mismo más rápido, porque prepara las reglas una sola vez; con sensitivity: "base", además, considera iguales «Ana» y «ana», o «canción» y «cancion» (por eso los dos ceros).
Objetos por varias claves
const productos = [
{ nombre: "Ratón", categoria: "periféricos", precio: 19.5 },
{ nombre: "Monitor", categoria: "pantallas", precio: 189 },
{ nombre: "Teclado", categoria: "periféricos", precio: 49.9 },
{ nombre: "Webcam", categoria: "periféricos", precio: 49.9 },
{ nombre: "Portátil", categoria: "equipos", precio: 899 },
];
const ordenados = productos.toSorted(
(a, b) => a.categoria.localeCompare(b.categoria, "es") || b.precio - a.precio
);
for (const p of ordenados) console.log(p.categoria.padEnd(12), String(p.precio).padStart(6), p.nombre);
console.log("el original sigue igual:", productos[0].nombre);salidaequipos 899 Portátil pantallas 189 Monitor periféricos 49.9 Teclado periféricos 49.9 Webcam periféricos 19.5 Ratón el original sigue igual: Ratón
El truco está en el ||: si la categoría es la misma, la primera comparación da 0, que cuenta como falso, y se usa la segunda, el precio de mayor a menor. Teclado y Webcam cuestan lo mismo y conservan su orden original: el ordenamiento de JavaScript es estable, lo garantiza el estándar.
sort cambia el array; toSorted no
const original = [3, 1, 2]; const ordenado = original.sort(); console.log(original, ordenado, original === ordenado); const otro = [3, 1, 2]; const copia = otro.toSorted(); console.log(otro, copia, otro === copia);
salida[ 1, 2, 3 ] [ 1, 2, 3 ] true [ 3, 1, 2 ] [ 1, 2, 3 ] false
sort() ordena el array original y además lo devuelve, así que ordenado y original son el mismo array. Es fácil desordenar sin querer datos que se usan en otro sitio. toSorted(), disponible en todos los navegadores actuales y en Node desde la versión 20, devuelve una copia ordenada y deja el original en paz. Lo mismo hacen toReversed() y toSpliced().
Buscar
const productos = [
{ id: 1, nombre: "Ratón", precio: 19.5 },
{ id: 2, nombre: "Monitor", precio: 189 },
{ id: 3, nombre: "Teclado", precio: 49.9 },
{ id: 4, nombre: "Webcam", precio: 49.9 },
];
console.log(productos.find((p) => p.precio > 40));
console.log(productos.findLast((p) => p.precio > 40));
console.log(productos.findIndex((p) => p.nombre === "Teclado"));
console.log(productos.find((p) => p.precio > 1000));
console.log(productos.findIndex((p) => p.precio > 1000));
console.log(productos.filter((p) => p.precio < 50).map((p) => p.nombre));
console.log(productos.some((p) => p.precio > 100), productos.every((p) => p.precio > 10));
const precios = [19.5, 189, 49.9, NaN];
console.log(precios.includes(189), precios.indexOf(189));
console.log(precios.includes(NaN), precios.indexOf(NaN));salida{ id: 2, nombre: 'Monitor', precio: 189 }
{ id: 4, nombre: 'Webcam', precio: 49.9 }
2
undefined
-1
[ 'Ratón', 'Teclado', 'Webcam' ]
true true
true 1
true -1| Método | Devuelve | Si no encuentra nada |
|---|---|---|
find / findLast | El primer / el último elemento que cumple | undefined |
findIndex | Su posición | -1 |
filter | Todos los que cumplen, en un array nuevo | [] |
some / every | Si alguno / todos cumplen | false / true |
includes | Si el valor está | false |
Las dos últimas líneas de la salida esconden un detalle: includes(NaN) encuentra el NaN, pero indexOf(NaN) devuelve -1. indexOf compara con ===, y NaN === NaN es falso; includes usa otra comparación que sí lo reconoce.
Agrupar
const productos = [
{ nombre: "Ratón", categoria: "periféricos" },
{ nombre: "Monitor", categoria: "pantallas" },
{ nombre: "Teclado", categoria: "periféricos" },
];
const grupos = Object.groupBy(productos, (p) => p.categoria);
console.log(grupos);salida[Object: null prototype] {
'periféricos': [
{ nombre: 'Ratón', categoria: 'periféricos' },
{ nombre: 'Teclado', categoria: 'periféricos' }
],
pantallas: [ { nombre: 'Monitor', categoria: 'pantallas' } ]
}Object.groupBy reparte los elementos según lo que devuelva la función. El «null prototype» indica que el objeto resultante no hereda nada, así que no hay riesgo de que una categoría se llame toString y choque con algo.
Muchas búsquedas: un Map
find recorre el array desde el principio cada vez. Con pocos elementos da igual; si vas a buscar muchas veces en una lista grande, conviene construir un Map una vez:
const productos = Array.from({ length: 100_000 }, (_, i) => ({ id: i + 1, nombre: `producto ${i + 1}` }));
let t = performance.now();
for (let i = 0; i < 1000; i++) productos.find((p) => p.id === 99_999);
console.log("find, 1,000 búsquedas:", (performance.now() - t).toFixed(1), "ms");
const porId = new Map(productos.map((p) => [p.id, p]));
t = performance.now();
for (let i = 0; i < 1000; i++) porId.get(99_999);
console.log("Map.get, 1,000 búsquedas:", (performance.now() - t).toFixed(2), "ms");
console.log(porId.get(99_999).nombre);salidafind, 1,000 búsquedas: 529.4 ms Map.get, 1,000 búsquedas: 0.03 ms producto 99999
Mil búsquedas en cien mil productos: medio segundo con find, frente a unas centésimas de milisegundo con el Map. Lo ejecutamos tres veces y los números apenas cambiaron. Construir el Map tiene un coste, pero se paga una sola vez.
Todo el código de esta página se ejecutó con Node.js 24.22 en un contenedor limpio antes de publicarla; las salidas, incluidos los errores, están copiadas de esa ejecución. Documentación oficial: Array.prototype.sort en MDN.