Skip to content
This page has been auto-translated and may contain errors.View in English

Métodos de arrays

docs.scrimba.com

Tienes una lista de precios y quieres una segunda lista con impuestos agregados a cada uno. Podrías escribir un bucle for que recorra el array, haga la suma, e inserte cada resultado en un nuevo array. Eso funciona, pero es mucho trabajo para una idea pequeña: toma cada elemento y cámbialo. Los métodos de arrays son las herramientas integradas para exactamente este tipo de trabajo. Te permiten decir qué quieres que se haga con una lista sin explicar el bucle que lo hace, y este capítulo cubre los que usarás a diario. Se construyen directamente sobre arrays y funciones, así que mantén ambos a mano.

map: cambia cada elemento

map es un método: una función que pertenece a un array y realiza una tarea en él. map crea un nuevo array ejecutando cada elemento a través de una pequeña función que le das, y recopila los resultados. Le pasas esa función, e itera cada elemento uno a la vez.

Digamos que tienes algunos precios y quieres que cada uno se duplique:

js
const precios = [2, 4, 6];
const duplicados = precios.map((precio) => precio * 2);
console.log(duplicados); // [4, 8, 12]

La parte (precio) => precio * 2 es una función flecha: una función corta que toma un elemento (precio) y devuelve en qué debe convertirse (precio * 2). map la llama una vez por elemento e inserta cada respuesta en el nuevo array, en el mismo orden.

map es un método de array que transforma una lista en una nueva lista de la misma longitud. Le das un callback, una función que map ejecuta una vez por elemento, y lo que ese callback devuelva se convierte en el elemento en la misma posición del resultado.

js
const precios = [2, 4, 6];
const duplicados = precios.map((precio) => precio * 2);
console.log(duplicados); // [4, 8, 12]

Compáralo con la versión manual, que hace lo mismo con más partes móviles:

js
const precios = [2, 4, 6];
const duplicados = [];
for (let i = 0; i < precios.length; i += 1) {
  duplicados.push(precios[i] * 2);
}
console.log(duplicados); // [4, 8, 12]

El bucle te obliga a gestionar un índice, un nuevo array, y un push. map expresa la intención, "convierte cada precio en precio por dos", y maneja la contabilidad por ti.

map transforma un array en un nuevo array de la misma longitud aplicando un callback (la función que le pasas para que la llame) a cada elemento. El valor devuelto del callback en el índice i se convierte en el elemento en el índice i del resultado. Nada se salta y nada se agrega, así que la forma se preserva y solo los valores cambian.

js
const precios = [2, 4, 6];
const conImpuestos = precios.map((precio) => precio * 1.2);
console.log(conImpuestos); // [2.4, 4.8, 7.2]

El callback también recibe el índice y el array completo como segundo y tercer argumentos, así que array.map((valor, índice) => ...) está disponible cuando la posición importa. El valor que mantiene para ti es intención: map dice "misma longitud, cada elemento transformado" de un vistazo, donde un bucle hace que el lector reconstruya eso desde un índice, un contador, y un push.

Junomapmap toma una lista y te da una nueva de la misma longitud, con cada elemento pasado por tu pequeña función. La función flecha dice en qué se convierte cada elemento, y map lo hace en todos ellos en orden. Úsalo cada vez que te encuentres escribiendo un bucle solo para construir una segunda lista.
Junomapmap es "transforma cada elemento en un nuevo array de la misma longitud". El callback se ejecuta una vez por elemento y su valor devuelto cae en la misma posición. Una vez que lo ves como reemplazo del patrón bucle-más-push, la mitad de tus bucles se convierten en una sola línea legible.
Junomapmap preserva la longitud y la posición: el callback devuelto en el índice i se convierte en el índice i del resultado. El callback obtiene el índice y el array también, para cuando la posición importa. Se lee como intención donde un bucle hace que rearmes esa intención desde un contador y un push.

filter: mantén los elementos que pasan

Donde map cambia cada elemento, filter mantiene solo algunos de ellos. Le das una función que responde sí o no para cada elemento, y filter construye un nuevo array solo de los elementos que obtuvieron un sí.

js
const calificaciones = [40, 75, 90, 55, 88];
const aprobadas = calificaciones.filter((calificacion) => calificacion >= 60);
console.log(aprobadas); // [75, 90, 88]

La función (calificacion) => calificacion >= 60 devuelve true o false para cada calificación. filter mantiene las que son true y descarta el resto. El nuevo array puede ser más corto que el original, y nunca cambia el orden.

filter devuelve un nuevo array que contiene solo los elementos cuyo callback devuelve true. El callback es un predicado: una función cuyo trabajo es responder una pregunta de sí o no sobre cada elemento.

js
const calificaciones = [40, 75, 90, 55, 88];
const aprobadas = calificaciones.filter((calificacion) => calificacion >= 60);
console.log(aprobadas); // [75, 90, 88]

El resultado es a menudo más corto que la entrada, y mantiene el orden original. Comparado con un bucle con un if adentro, filter pone la prueba al frente y elimina la ceremonia. Si el callback devuelve true para todo obtienes una copia completa; si devuelve false para todo obtienes un array vacío.

filter devuelve un nuevo array de los elementos para los que el predicado (el callback cuyo trabajo es devolver una respuesta truthy o falsy) se mantiene. La longitud no se preserva: el resultado va desde vacío hasta una copia completa, y el orden se mantiene.

js
const calificaciones = [40, 75, 90, 55, 88];
const aprobadas = calificaciones.filter((calificacion) => calificacion >= 60);
console.log(aprobadas); // [75, 90, 88]

Hay dos cosas que vale la pena mantener claras. Primero, filter decide si un elemento sobrevive; nunca cambia el elemento, así que combínalo con map cuando necesites tanto seleccionar como transformar. Segundo, el valor devuelto del callback se coerciona a un booleano, así que un predicado que accidentalmente devuelve 0 o undefined descarta elementos que pretendías mantener. Devuelve una comparación explícita y esa clase de error desaparece.

Junofilterfilter mantiene los elementos que pasan una prueba y descarta el resto, así que el nuevo array es a menudo más corto. Tu función responde sí o no para cada elemento, y solo los síes pasan. Nunca reordena nada, así que lo que sobrevive permanece en el orden en que comenzó.
Junofilterfilter mantiene elementos cuyo callback devuelve true, dándote un nuevo array que tiene la misma longitud o es más corto. El callback es un predicado: una prueba de sí o no, no un transformador. Todo true da una copia completa, todo false da un array vacío, y el orden siempre se preserva.
Junofilterfilter selecciona, map transforma, y mantener esa separación clara es por qué se encadenan tan bien. El callback devuelto se coerciona a un booleano, así que un predicado que deja escapar un 0 o undefined descarta silenciosamente elementos que querías. Devuelve una comparación explícita y toda esa clase de error desaparece.

reduce: colapsalo a un valor

map y filter ambos te devuelven un array. reduce es el que colapsa un array a un solo valor: un total, un máximo, una cadena unida. Desconcierta a más gente que los otros, así que vale la pena ir lento.

reduce recorre el array mientras lleva un resultado en ejecución junto con él. En cada elemento pregunta: dado el resultado hasta ahora, y este elemento, ¿cuál es el nuevo resultado?

js
const precios = [10, 20, 30];
const total = precios.reduce((suma, precio) => suma + precio, 0);
console.log(total); // 60

La función obtiene dos cosas: suma, el total en ejecución hasta ahora, y precio, el elemento actual. Devuelve suma + precio, que se convierte en el nuevo total en ejecución para el siguiente elemento. El 0 al final es donde comienza el total. Así que va 0 + 10, luego 10 + 20, luego 30 + 30, llegando a 60.

reduce convierte un array entero en un valor llevando un acumulador: un resultado en ejecución que cada paso actualiza y pasa. El callback recibe el acumulador y el elemento actual, y devuelve el acumulador para el siguiente paso.

js
const precios = [10, 20, 30];
const total = precios.reduce((suma, precio) => suma + precio, 0);
console.log(total); // 60

Dos argumentos para mantener claros: suma es el acumulador (el valor construido hasta ahora), precio es el elemento actual. El segundo argumento a reduce, el 0, es el valor inicial del acumulador. Paso a paso: comienza en 0, suma 10 para obtener 10, suma 20 para obtener 30, suma 30 para obtener 60. El nombre suma es solo una etiqueta aquí; el acumulador puede ser cualquier tipo, incluyendo una cadena a la que sigues agregando o un máximo en ejecución.

reduce dobla un array en un solo valor pasando un acumulador (el resultado en ejecución) a través del array. El callback es (acumulador, elementoActual) => siguienteAcumulador, y su valor devuelto se convierte en el acumulador pasado al siguiente paso. El segundo argumento a reduce es el acumulador inicial.

js
const precios = [10, 20, 30];
const total = precios.reduce((suma, precio) => suma + precio, 0);
console.log(total); // 60

Dos reglas prácticas ahorran la mayoría de errores de reduce. Siempre pasa el valor inicial: sin él, reduce usa el primer elemento como semilla y comienza en el segundo, lo que lanza en un array vacío y cambia silenciosamente el comportamiento cuando tu tipo de acumulador difiere del tipo de elemento. Y cada ruta a través del callback debe devolver el acumulador; olvidar el return en un cuerpo multilineal deja undefined como acumulador para el siguiente paso. reduce es el más general de estos métodos, map y filter pueden ambos escribirse como un reduce, que es exactamente por qué también es el más fácil de hacer ilegible. Úsalo cuando estés colapsando a un valor, y opta por algo más simple cuando no lo estés.

Junoreducereduce lleva un resultado en ejecución a través de la lista y lo actualiza en cada elemento. El callback obtiene el resultado hasta ahora más el elemento actual, y devuelve el nuevo resultado. El último argumento es donde comienzas, usualmente 0 para un total, y este me hizo clic en el momento en que lo leí en voz alta paso a paso.
Junoreducereduce reduce un array a un valor usando un acumulador: un resultado en ejecución que cada paso actualiza y pasa. El callback es (acumulador, elemento) y el último argumento siembra el acumulador. No tiene que ser un número; el acumulador puede ser una cadena que construyes o un máximo en ejecución.
Junoreduce Siempre pasa el valor inicial, o reduce siembra desde el primer elemento y lanza en un array vacío. Cada ruta a través del callback debe devolver el acumulador, o el siguiente paso obtiene undefined. Es el más general de estos métodos y el más fácil de hacer ilegible, así que úsalo para colapsar a un valor y opta por algo más simple cuando no lo estés.

find, some, y every

Estos tres son parientes cercanos de filter: ejecutan una prueba sobre el array, pero en lugar de un nuevo array responden una pregunta más estrecha.

find te da el primer elemento que pasa la prueba, no una lista de todos ellos. some y every responden preguntas verdadero-o-falso sobre todo el array.

js
const calificaciones = [40, 75, 90, 55];

const primerAprobado = calificaciones.find((calificacion) => calificacion >= 60);
console.log(primerAprobado); // 75

const algunoAprobado = calificaciones.some((calificacion) => calificacion >= 60);
console.log(algunoAprobado); // true

const todosAprobados = calificaciones.every((calificacion) => calificacion >= 60);
console.log(todosAprobados); // false

find devuelve el primer elemento coincidente (75). some pregunta "¿pasó algún elemento?" y devuelve true porque al menos uno lo hizo. every pregunta "¿pasaron todos?" y devuelve false porque 40 y 55 no lo hicieron.

Mismo estilo de predicado que filter, tres respuestas diferentes. find devuelve el primer elemento coincidente (o undefined si ninguno coincide). some devuelve true si al menos un elemento pasa. every devuelve true solo si todos pasan.

js
const calificaciones = [40, 75, 90, 55];

console.log(calificaciones.find((calificacion) => calificacion >= 60)); // 75
console.log(calificaciones.some((calificacion) => calificacion >= 60)); // true
console.log(calificaciones.every((calificacion) => calificacion >= 60)); // false

Opta por find cuando quieras un elemento en lugar de una lista filtrada, y some o every cuando solo necesites una respuesta de sí o no sobre la colección. Usar filter(...).length > 0 donde some haría el trabajo funciona, pero comprueba cada elemento y construye un array desechable para responder una pregunta que some responde directamente.

Tres consultas impulsadas por predicados que se detienen temprano en lugar de construir un nuevo array. find devuelve el primer elemento coincidente o undefined. some devuelve true en el primer elemento que pasa. every devuelve false en el primero que falla.

js
const calificaciones = [40, 75, 90, 55];

console.log(calificaciones.find((calificacion) => calificacion >= 60)); // 75
console.log(calificaciones.some((calificacion) => calificacion >= 60)); // true
console.log(calificaciones.every((calificacion) => calificacion >= 60)); // false

El corto circuito es el punto: some y every se detienen en el momento en que se decide la respuesta, así que son más baratos que un filter sobre todo el array cuando solo necesitas un booleano. Dos casos límite a saber: find devolviendo undefined es indistinguible de encontrar un undefined real, así que prueba el predicado que te importa en lugar del valor devuelto cuando eso es posible. Y en un array vacío some siempre es false y every siempre es true (vacíamente, ya que no hay elemento que falle), que es correcto pero sorprende a la gente la primera vez.

Junofind, some, y everyfind te da el primer elemento que pasa, no una lista completa. some pregunta "¿alguno pasó?" y every pregunta "¿todos pasaron?", y ambos responden con true o false. Usa estos cuando quieras un elemento o una respuesta de sí o no, no un array filtrado.
Junofind, some, y every Mismo predicado que filter, tres respuestas más estrechas: find da la primera coincidencia o undefined, some es verdadero si alguno pasa, every es verdadero solo si todos pasan. Opta por estos en lugar de filter(...).length cuando quieras un elemento o un booleano, ya que dicen la intención y evitan construir un array desechable.
Junofind, some, y everysome y every hacen corto circuito, deteniéndose en el momento en que se decide la respuesta, así que vencen a filtrar todo el array para un booleano. Observa dos límites: find devolviendo undefined se ve igual a encontrar un undefined real, y en un array vacío some siempre es falso mientras que every siempre es verdadero. Ambos son correctos, ambos sorprenden a la gente una vez.

Encadenamiento y manteniendo legibilidad

Los métodos anteriores comparten un diseño que se compensa una vez que los combinas: cada uno devuelve un nuevo valor y deja el array original intacto.

map, filter, y el resto nunca cambian el array con el que comenzaste. Te dan un nuevo array (o un valor) y dejan el original solo.

js
const precios = [10, 20, 30];
const duplicados = precios.map((precio) => precio * 2);
console.log(duplicados); // [20, 40, 60]
console.log(precios); // [10, 20, 30]

Porque map y filter ambos devuelven un array, puedes alinearlos: primero filtra, luego mapea los sobrevivientes.

js
const precios = [10, 20, 30, 40];
const grandesDuplicados = precios.filter((precio) => precio >= 20).map((precio) => precio * 2);
console.log(grandesDuplicados); // [40, 60, 80]

Estos métodos devuelven nuevos arrays y valores sin cambiar el original, una propiedad llamada inmutabilidad: tus datos fuente permanecen como eran. Eso es lo que hace el encadenamiento seguro. Porque filter devuelve un array, puedes llamar map directamente en su resultado:

js
const precios = [10, 20, 30, 40];
const grandesDuplicados = precios.filter((precio) => precio >= 20).map((precio) => precio * 2);
console.log(grandesDuplicados); // [40, 60, 80]

Lee una cadena de arriba a abajo como un pipeline: mantén precios de 20 o más, luego duplica cada sobreviviente. El orden importa, ya que filtrar primero significa que map se ejecuta sobre menos elementos. El encadenamiento se lee bien hasta dos o tres pasos; pasado eso, una variable intermedia nombrada o un bucle simple es a menudo más claro que una larga cadena.

Estos métodos no son mutantes: devuelven nuevos arrays y valores, dejando la fuente intacta. Esa inmutabilidad es lo que hace que una cadena filter(...).map(...) sea segura de leer como un pipeline, ya que ningún paso se devuelve y edita un array compartido bajo ti.

js
const precios = [10, 20, 30, 40];
const grandesDuplicados = precios.filter((precio) => precio >= 20).map((precio) => precio * 2);
console.log(grandesDuplicados); // [40, 60, 80]

Dos llamadas de juicio deciden si una cadena ayuda o daña. Primero, costo: cada paso recorre el array y asigna uno nuevo, así que una cadena de tres pasos hace tres pasadas y dos arrays intermedios. En los tamaños que encuentras día a día eso es irrelevante y las ganancias de legibilidad ganan; en una ruta crítica sobre un array grande puede importar, y un solo bucle o reduce que hace el trabajo en una pasada es la respuesta. Segundo, claridad: una cadena gana su lugar cuando cada paso es una idea clara. El momento en que un callback crece un cuerpo con ramas, o la cadena corre pasado tres pasos, un bucle for simple con una variable bien nombrada usualmente se lee mejor. Prefiere la versión que un compañero entienda más rápido, no la que cabe en una línea.

JunoEncadenamiento y manteniendo legibilidad Estos métodos nunca cambian el array con el que comienzas, te dan uno nuevo, así que el original siempre es seguro. Porque filter y map ambos devuelven arrays, puedes encadenarlos: filtra primero, luego mapea lo que queda. Lee la cadena de arriba a abajo, un paso a la vez.
JunoEncadenamiento y manteniendo legibilidad Inmutabilidad, dejando el array original solo, es lo que hace el encadenamiento seguro. Lee filter(...).map(...) como un pipeline de arriba a abajo, y recuerda que el orden importa ya que filtrar primero significa mapear menos elementos. Pasado dos o tres pasos, una variable nombrada o un bucle simple usualmente se lee más claro que una larga cadena.
JunoEncadenamiento y manteniendo legibilidad Cada paso es una pasada y una asignación, así que una cadena está bien en tamaños cotidianos y vale la pena colapsarla en un bucle o un reduce en una ruta crítica sobre datos grandes. La claridad es la otra llamada: una cadena gana su lugar cuando cada paso es una idea clara, y un bucle simple gana una vez que un callback crece ramas. Escribe la versión que un compañero lee más rápido, no la más corta.

Por dónde seguir

map, filter, y reduce cubren la mayoría de lo que harás con una lista: transformarla, reducirla, o colapsarla. find, some, y every responden las preguntas más estrechas. Una vez que estos se sienten naturales, la mayoría de tus bucles for sobre un array se convierten en una línea o dos que dice lo que quisiste decir.

El siguiente paso natural es objetos, donde los arrays comienzan a mantener elementos más ricos que simples números y cadenas, y estos mismos métodos siguen funcionando en ellos. Si cualquiera de la sintaxis callback aquí se sintió inestable, una pasada por funciones hace que el resto haga clic.