`Objeto` Array

Este artículo explica sobre el objeto Array.

Explicaré usos prácticos de los arrays paso a paso de forma fácil de entender.

YouTube Video

Objeto Array

El objeto Array de JavaScript es una de las estructuras más importantes que forman la base de todo tipo de procesamiento de datos. Desde operaciones básicas con arrays hasta funciones de orden superior útiles para una transformación eficiente de los datos, hay muchas funcionalidades que debes conocer.

Fundamentos de los Arrays

En JavaScript, los arrays son una estructura de datos fundamental para manejar varios valores juntos. Aquí presentamos cómo crear arrays y cómo leer y escribir sus elementos con ejemplos sencillos.

 1// Create arrays in different ways
 2const arr1 = [1, 2, 3];           // array literal
 3const arr2 = new Array(4, 5, 6);  // Array constructor
 4const arr3 = Array.of(7, 8, 9);   // Array.of
 5
 6console.log("arr1 created with literal.   :", arr1);
 7console.log("arr2 created with constructor:", arr2);
 8console.log("arr3 created with Array.of.  :", arr3);
 9// arr1 created with literal.   : [ 1, 2, 3 ]
10// arr2 created with constructor: [ 4, 5, 6 ]
11// arr3 created with Array.of.  : [ 7, 8, 9 ]
12
13// Access and modify elements
14let first = arr1[0];              // read element
15console.log("First element of arr1:", first);
16// First element of arr1: 1
17
18arr1[1] = 20;                     // modify element
19console.log("arr1 after modifying index 1:", arr1);
20// arr1 after modifying index 1: [ 1, 20, 3 ]
21
22const len = arr1.length;          // get length
23console.log("Length of arr1:", len);
24// Length of arr1: 3
  • Este código muestra tres formas de crear arrays, cómo leer y actualizar elementos usando índices y cómo obtener la longitud utilizando la propiedad length.
  • Los literales de arreglos son los más comunes y legibles, y se utilizan con mayor frecuencia en situaciones cotidianas.

Agregar y Eliminar Elementos (al Final o al Principio)

Los arrays permiten añadir o eliminar elementos fácilmente al final o al principio. Estas operaciones también son útiles al implementar estructuras como pilas o colas.

 1// Push and pop (stack-like)
 2const stack = [];
 3console.log("Initial stack:", stack);
 4
 5stack.push(1);                    // push 1
 6console.log("After push(1):", stack);
 7
 8stack.push(2);                    // push 2
 9console.log("After push(2):", stack);
10
11const last = stack.pop();         // pop -> 2
12console.log("Popped value:", last);
13console.log("Stack after pop():", stack);
14
15// Unshift and shift (queue-like)
16const queue = [];
17console.log("Initial queue:", queue);
18
19queue.push('a');                  // add to end
20console.log("After push('a'):", queue);
21
22queue.unshift('start');           // add to front
23console.log("After unshift('start'):", queue);
24
25const firstItem = queue.shift();  // remove from front
26console.log("Shifted value:", firstItem);
27console.log("Queue after shift():", queue);
28
29// Initial stack: []
30// After push(1): [ 1 ]
31// After push(2): [ 1, 2 ]
32// Popped value: 2
33// Stack after pop(): [ 1 ]
34
35// Initial queue: []
36// After push('a'): [ 'a' ]
37// After unshift('start'): [ 'start', 'a' ]
38// Shifted value: start
39// Queue after shift(): [ 'a' ]
  • push y pop operan en el final de un array. Son ideales para implementar estructuras tipo pila.
  • unshift y shift operan en el principio de un array. Sin embargo, ten en cuenta que operar sobre el principio requiere cambiar todos los índices de los elementos internamente, lo que lo convierte en una operación costosa.

Manejo de Elementos en el Medio (splice y slice)

Al manejar elementos en medio de un array, elige entre splice y slice dependiendo de si quieres modificar el array original o no. Si solo quieres extraer una parte de un arreglo, usa slice; si quieres modificar el propio arreglo, como insertar o eliminar elementos, usa splice.

 1// slice (non-destructive)
 2const nums = [0, 1, 2, 3, 4];
 3console.log("Original nums:", nums);
 4
 5const part = nums.slice(1, 4); // returns [1, 2, 3]
 6console.log("Result of nums.slice(1, 4):", part);
 7console.log("nums after slice (unchanged):", nums);
 8
 9// splice (destructive)
10const arr = [10, 20, 30, 40];
11console.log("\nOriginal arr:", arr);
12
13// remove 1 item at index 2, insert 25 and 27
14arr.splice(2, 1, 25, 27);
15console.log("After arr.splice(2, 1, 25, 27):", arr);
16
17// Original nums: [ 0, 1, 2, 3, 4 ]
18// Result of nums.slice(1, 4): [ 1, 2, 3 ]
19// nums after slice (unchanged): [ 0, 1, 2, 3, 4 ]
20
21// Original arr: [ 10, 20, 30, 40 ]
22// After arr.splice(2, 1, 25, 27): [ 10, 20, 25, 27, 40 ]
  • slice solo extrae elementos y no modifica el array original.
  • splice agrega o elimina elementos y modifica el array en sí, así que ten especial cuidado con su impacto en el comportamiento.

Iteración (for / for...of / forEach)

Hay varias formas de procesar arrays en secuencia, y puedes elegir según el propósito y tu preferencia de estilo de codificación. Aquí tienes tres estructuras de bucle típicas.

 1const items = ['apple', 'banana', 'cherry'];
 2console.log("Items:", items);
 3
 4// classic for
 5console.log("\n--- Classic for loop ---");
 6for (let i = 0; i < items.length; i++) {
 7  console.log(`Index: ${i}, Value: ${items[i]}`);
 8}
 9
10// for...of (recommended for values)
11console.log("\n--- for...of loop ---");
12for (const item of items) {
13  console.log(`Value: ${item}`);
14}
15
16// forEach (functional style)
17console.log("\n--- forEach loop ---");
18items.forEach((item, index) => {
19  console.log(`Index: ${index}, Value: ${item}`);
20});
  • El bucle for es el más flexible, permitiendo operaciones con índices y un control detallado de la iteración usando break y otras sentencias.
  • for...of proporciona una forma concisa de manejar valores de elementos y es el más equilibrado en cuanto a legibilidad.
  • forEach permite un código de estilo funcional y es adecuado para operaciones con efectos secundarios, como el registro o la actualización de datos para cada elemento. Sin embargo, ten en cuenta que no puedes usar break ni continue, y no es adecuado para el procesamiento asíncrono con await.

map / filter / reduce — Funciones de Orden Superior

map, filter y reduce son funciones de orden superior que se utilizan frecuentemente para transformar, filtrar o agregar arreglos. Como puedes expresar claramente los procesos repetitivos, tu código se vuelve simple y fácil de entender.

 1const numbers = [1, 2, 3, 4, 5];
 2console.log("Original numbers:", numbers);
 3
 4// map: transform each item
 5const doubled = numbers.map(n => n * 2);
 6console.log("\nResult of map (n * 2):", doubled);
 7
 8// filter: select items
 9const evens = numbers.filter(n => n % 2 === 0);
10console.log("Result of filter (even numbers):", evens);
11
12// reduce: accumulate to single value
13const sum = numbers.reduce((acc, n) => acc + n, 0);
14console.log("Result of reduce (sum):", sum);
15
16// Original numbers: [ 1, 2, 3, 4, 5 ]
17// Result of map (n * 2): [ 2, 4, 6, 8, 10 ]
18// Result of filter (even numbers): [ 2, 4 ]
19// Result of reduce (sum): 15
  • Estos métodos te permiten centrarte en lo que quieres hacer de manera declarativa, mejorando la legibilidad y ayudando a evitar efectos secundarios no deseados.

find / findIndex / some / every

Aquí tienes una visión general de los métodos de búsqueda y comprobación de condiciones. Estos son útiles para encontrar elementos que cumplan ciertas condiciones o realizar comprobaciones booleanas sobre el conjunto.

 1const users = [
 2  { id: 1, name: 'Alice' },
 3  { id: 2, name: 'Bob' },
 4  { id: 3, name: 'Carol' }
 5];
 6
 7console.log("Users:", users);
 8
 9// Find the first user whose name is 'Bob'
10const bob = users.find(user => user.name === 'Bob');
11console.log("\nResult of find (name === 'Bob'):", bob);
12
13// Find index of the user whose id is 3
14const indexOfId3 = users.findIndex(user => user.id === 3);
15console.log("Result of findIndex (id === 3):", indexOfId3);
16
17// Check if there exists a user with id = 2
18const hasId2 = users.some(user => user.id === 2);
19console.log("Result of some (id === 2):", hasId2);
20
21// Check if all users have a numeric id
22const allHaveNumericId = users.every(user => typeof user.id === 'number');
23console.log("Result of every (id is number):", allHaveNumericId);
24// Result of find (name === 'Bob'): { id: 2, name: 'Bob' }
25// Result of findIndex (id === 3): 2
26// Result of some (id === 2): true
27// Result of every (id is number): true
  • find devuelve el primer elemento que cumple la condición.
  • findIndex devuelve el índice del elemento que cumple la condición.
  • some devuelve true si hay al menos un elemento que satisface la condición.
  • every devuelve true si todos los elementos cumplen la condición.

Todos estos son muy útiles en el procesamiento de arrays, así que usarlos adecuadamente para cada situación mantendrá tu código conciso y claro.

Ordenación y Funciones de Comparación

Los arrays se ordenan con sort, pero por defecto compara los elementos como cadenas, lo que puede dar resultados inesperados al ordenar números.

 1const nums = [10, 2, 33, 4];
 2console.log("Original nums:", nums);
 3
 4// Default sort: compares elements as strings (not suitable for numbers)
 5nums.sort();
 6console.log("\nAfter default sort (string comparison):", nums);
 7
 8// Numeric ascending sort using a compare function
 9nums.sort((a, b) => a - b);
10console.log("After numeric sort (a - b):", nums);
11
12// Sort objects by a property
13const people = [{ age: 30 }, { age: 20 }, { age: 25 }];
14console.log("\nOriginal people:", people);
15
16people.sort((a, b) => a.age - b.age);
17console.log("After sorting people by age:", people);
18
19// After default sort (string comparison): [ 10, 2, 33, 4 ]
20// After numeric sort (a - b): [ 2, 4, 10, 33 ]
21// Original people: [ { age: 30 }, { age: 20 }, { age: 25 } ]
22// After sorting people by age: [ { age: 20 }, { age: 25 }, { age: 30 } ]
  • Al ordenar números u objetos, siempre especifica una función de comparación para ordenarlos en el orden deseado.
  • En una función de comparación, un valor de retorno negativo coloca a antes de b, uno positivo coloca b antes de a, y 0 mantiene su orden.

Copia e Inmutabilidad de Arrays

Al copiar arreglos, es importante entender la diferencia entre 'copia por referencia' y 'copia superficial'. En particular, ten en cuenta que si hay objetos dentro del array, una copia superficial hará que los objetos internos sean compartidos.

Copia por referencia

Cuando asignas un arreglo a otra variable, el arreglo en sí no se duplica; en su lugar, se copia la 'referencia' que apunta al mismo arreglo.

 1const a = [1, 2, 3];
 2console.log("Original a:", a);
 3
 4// Reference copy; modifying b also affects a
 5const b = a;
 6console.log("\nReference copy b = a:", b);
 7// Reference copy b = a: [ 1, 2, 3 ]
 8
 9b[0] = 100;
10console.log("After modifying b[0] = 100:");
11console.log("a:", a);  // a: [ 100, 2, 3 ] (affected)
12console.log("b:", b);  // b: [ 100, 2, 3 ]
  • Con una copia por referencia, si modificas el contenido del arreglo usando la variable copiada, esos cambios también se reflejarán en la variable original que se refiere al mismo arreglo.

Copia superficial

Usar slice() o la sintaxis spread crea una 'copia superficial' porque solo se duplican los valores; los arreglos original y copiado se tratan como entidades separadas.

 1const a = [1, 2, 3];
 2console.log("Original a:", a);
 3
 4// Shallow copy (new array)
 5const b = a.slice();
 6console.log("\nShallow copy using slice():", b);
 7// Shallow copy using slice(): [ 100, 2, 3 ]
 8
 9const c = [...a];
10console.log("Shallow copy using spread [...a]:", c);
11// Shallow copy using spread [...a]: [ 100, 2, 3 ]
12
13// Modifying c or d does NOT affect a
14b[1] = 200;
15c[2] = 300;
16console.log("\nAfter modifying b[1] = 200 and c[2] = 300:");
17console.log("a:", a);   // [ 100, 2, 3 ]
18console.log("b:", b);   // [ 100, 200, 3 ]
19console.log("c:", c);   // [ 100, 2, 300 ]
  • Este código demuestra que una copia superficial de un arreglo creada con slice() o la sintaxis spread no afecta al arreglo original.

Copia superficial e inmutabilidad

Incluso si duplicas un arreglo usando una 'copia superficial', pueden producirse comparticiones no deseadas si el arreglo contiene objetos en su interior.

 1// Shallow copy doesn't clone inner objects
 2const nested = [{ x: 1 }, { x: 2 }];
 3console.log("\nOriginal nested:", nested);
 4// Original nested: [ { x: 1 }, { x: 2 } ]
 5
 6const shallow = nested.slice();
 7console.log("Shallow copy of nested:", shallow);
 8// Shallow copy of nested: [ { x: 1 }, { x: 2 } ]
 9
10// Changing inner object affects both arrays
11shallow[0].x = 99;
12console.log("\nAfter shallow[0].x = 99:");
13console.log("nested:", nested);
14console.log("shallow:", shallow);
15// nested: [ { x: 99 }, { x: 2 } ]
16// shallow: [ { x: 99 }, { x: 2 } ]
  • Este código demuestra que con una copia superficial, los objetos internos se comparten, por lo que modificar esos objetos internos afecta tanto al arreglo original como a la copia.
  • Si necesitas datos independientes, necesitas una 'copia profunda', como usando structuredClone() o conversión a JSON.

Métodos utilitarios útiles

Los siguientes son métodos utilitarios frecuentemente utilizados al trabajar con arrays. Usándolos adecuadamente para tu propósito, puedes escribir código corto y legible.

includes

El método includes verifica si un valor específico está incluido en un arreglo.

1// includes: check if array contains a value
2const letters = ['a', 'b', 'c'];
3const hasB = letters.includes('b');
4console.log("letters:", letters);  // [ 'a', 'b', 'c' ]
5console.log("letters.includes('b'):", hasB); // true
  • En este código, el método includes se utiliza para determinar de manera concisa si un valor especificado existe dentro del arreglo.

concat

El método concat devuelve un nuevo arreglo que agrega al final el arreglo o los valores especificados, manteniendo el arreglo original sin cambios.

1// concat: merge arrays without mutation
2const a1 = [1, 2];
3const a2 = [3, 4];
4const combined = a1.concat(a2);
5console.log("a1.concat(a2):", combined); // [ 1, 2, 3, 4 ]
6console.log("a1(unchanged):", a1);       // [ 1, 2 ]
  • Este código muestra que concat no es destructivo, lo que te permite generar un nuevo arreglo y preservar el original.

flat

Usando el método flat, puedes aplanar arreglos anidados.

1// flat and flatMap: flatten arrays or map + flatten in one step
2const nested = [1, [2, [3]]];
3console.log("nested:", nested); // nested: [ 1, [ 2, [ 3 ] ] ]
4console.log("nested.flat():", nested.flat()); // default depth = 1
5// nested.flat(): [ 1, 2, [ 3 ] ]
  • Este código muestra el resultado de aplanar un arreglo en un nivel.
  • Como flat te permite especificar la profundidad, puedes resolver la anidación de forma flexible según sea necesario.

flatMap

El método flatMap aplica una transformación a cada elemento y luego aplana automáticamente los resultados en un arreglo unidimensional.

1// flat and flatMap: flatten arrays or map + flatten in one step
2const words = ['hello world', 'hi'];
3console.log("words:", words); // [ 'hello world', 'hi' ]
4
5const splitWords = words.flatMap(w => w.split(' '));
6console.log("words.flatMap(w => w.split(' ')):", splitWords);
7// words.flatMap(w => w.split(' ')): [ 'hello', 'world', 'hi' ]
  • Este código muestra un ejemplo donde cada cadena en un arreglo se divide por espacios y los resultados se combinan y se aplanan en un solo arreglo.

join

El método join crea una cadena concatenando los elementos de un arreglo con un separador especificado.

1// join: combine elements into a string with a separator
2const csv = ['a', 'b', 'c'].join(',');
3console.log("['a', 'b', 'c'].join(','):", csv);  // a,b,c
  • En este código, el método join se usa para convertir un arreglo en una cadena separada por comas.

Errores Comunes

Las operaciones con arrays pueden parecer simples a primera vista, pero hay varios puntos que fácilmente pueden llevar a comportamientos no deseados. Muchos errores son fáciles de pasar por alto durante operaciones diarias con arrays, así que tener en cuenta los siguientes puntos mejorará considerablemente la fiabilidad de tu código.

  • El sort() de Array ordena por comparación de cadenas por defecto. Cuando ordenes números correctamente, siempre debes proporcionar una función de comparación.
  • Copiar arrays (mediante slice o la sintaxis de propagación, etc.) crea una copia superficial. Si tus arrays contienen objetos, ten cuidado porque los datos originales pueden cambiarse sin querer.
  • splice es un método destructivo que cambia directamente el array, mientras que slice es un método no destructivo que no modifica el array original. Es importante usarlos adecuadamente según tus necesidades.
  • forEach no es adecuado para bucles con procesamiento asíncrono utilizando await. Si quieres ejecutar el procesamiento asíncrono de forma fiable y en orden, se recomienda utilizar for...of.

Ejemplo Práctico

A continuación, un ejemplo que combina métodos de arrays para 'obtener la suma total de edades de los datos, extraer los mayores de 30 años y crear una lista de nombres.'.

 1const users = [
 2  { name: 'Alice', age: 28 },
 3  { name: 'Bob', age: 34 },
 4  { name: 'Carol', age: 41 },
 5  { name: 'Dave', age: 19 }
 6];
 7
 8console.log("Users:", users);
 9
10// sum ages
11const totalAge = users.reduce((acc, u) => acc + u.age, 0);
12console.log("\nTotal age of all users:", totalAge);
13// Total age of all users: 122
14
15// filter and map names of users 30 and older
16const namesOver30 = users
17  .filter(u => u.age >= 30)
18  .map(u => u.name);
19
20console.log("Users aged 30 or older (names):", namesOver30);
21// Users aged 30 or older (names): [ 'Bob', 'Carol' ]
  • Encadenando reduce, filter y map, puedes escribir de forma sencilla la agregación, extracción por condición y transformación de datos.
  • Este tipo de 'pipeline de procesamiento de datos' es altamente legible y, al tener pocos efectos secundarios, es común en aplicaciones reales.

Resumen

Con los arrays en JavaScript, incluso las operaciones básicas son ampliamente aplicables, y usando funciones de orden superior, tu código se vuelve más conciso y expresivo. Hay muchos conceptos que entender, pero una vez que domines cómo usar cada uno apropiadamente, el procesamiento de datos será mucho más fluido.

Puedes seguir el artículo anterior utilizando Visual Studio Code en nuestro canal de YouTube. Por favor, también revisa nuestro canal de YouTube.

YouTube Video