`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' ]
pushypopoperan en el final de un array. Son ideales para implementar estructuras tipo pila.unshiftyshiftoperan 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 ]
slicesolo extrae elementos y no modifica el array original.spliceagrega 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
fores el más flexible, permitiendo operaciones con índices y un control detallado de la iteración usandobreaky otras sentencias. for...ofproporciona una forma concisa de manejar valores de elementos y es el más equilibrado en cuanto a legibilidad.forEachpermite 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 usarbreaknicontinue, y no es adecuado para el procesamiento asíncrono conawait.
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
finddevuelve el primer elemento que cumple la condición.findIndexdevuelve el índice del elemento que cumple la condición.somedevuelvetruesi hay al menos un elemento que satisface la condición.everydevuelvetruesi 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
aantes deb, uno positivo colocabantes dea, 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
includesse 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
concatno 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
flatte 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
joinse 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()deArrayordena por comparación de cadenas por defecto. Cuando ordenes números correctamente, siempre debes proporcionar una función de comparación. - Copiar arrays (mediante
sliceo 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. splicees un método destructivo que cambia directamente el array, mientras queslicees un método no destructivo que no modifica el array original. Es importante usarlos adecuadamente según tus necesidades.forEachno es adecuado para bucles con procesamiento asíncrono utilizandoawait. Si quieres ejecutar el procesamiento asíncrono de forma fiable y en orden, se recomienda utilizarfor...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,filterymap, 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.