Arrays

<< Colecciones de datos Rest/Spread >>

En JavaScript, los arrays son un tipo de objetos y por tanto, son instancias de la clase Array. Estos arrays son dinámicos, por lo que no tienen un tamaño fijo, es decir, podemos crearlo con un tamaño inicial y luego añadirle más elementos.

El constructor puede recibir 0 parámetros (array vacío), 1 número (el tamaño del array), o en cualquier otro caso, se crearía un array con los elementos recibidos. Debemos tener en cuenta que en JavaScript un array puede contener al mismo tiempo diferentes tipos de datos: number, string, boolean, object, etc.

let a = new Array(); // Crea un array vacío
a[0] = 13; // Asigna la primera posición del array
console.log(a.length); // Imprime 1
console.log(a[0]); // Imprime 13
console.log(a[1]); // Imprime undefined (posición no asignada aún)

Fíjate que cuando accedes a una posición del array que no ha sido definida, devuelve undefined. La longitud de un array depende de la última posición que ha sido asignada. Vamos a ver un ejemplo de lo que ocurre cuando asignas una posición mayor que la longitud y que no es consecutiva al último valor asignado.

let  a = new Array(12); // Crea un array de tamaño 12
console.log(a.length); // Imprime 12
a[20] = "Hello";
console.log(a.length); // Ahora imprime 21 (0-20). Las posiciones 0-19 tendrán el valor undefined 

Podemos reducir la longitud del array modificando directamente la propiedad de la longitud del array (length). Si reducimos la longitud de un array, las posiciones mayores a la nueva longitud serán consideradas como undefined (borradas).

let  a = new Array("a", "b", "c", "d", "e"); // Array con 5 valores
console.log(a[3]); // Imprime "d"
a.length = 2; // Posiciones 2-4 serán destruidas
console.log(a[3]); // Imprime undefined

Puedes crear un array usando directamente la notación de corchetes en lugar de usar new Array(). Los valores que pongamos dentro (opcional), separados por coma, serán los elementos iniciales.

let  a = ["a", "b", "c", "d", "e"]; // Array de tamaño 5, con 5 valores inicialmente
console.log(typeof a); // Imprime object
console.log(a instanceof Array); // Imprime true. a es una instancia de array
a[a.length] = "f"; // Insertamos un nuevo elemento al final
console.log(a); // Imprime ["a", "b", "c", "d", "e", "f"]

Recorriendo arrays

Podemos recorrer un array con los clásicos bucles while y for, creando un contador para el índice que iremos incrementando en cada iteración. Otra versión del for, es el bucle for..in. Con este bucle podemos iterar los índices de un array sin necesidad de crear una variable contador.

let  ar = [4, 21, 33, 24, 8];

let i = 0;
while(i < ar.length) { // Imprime 4 21 33 24 8
    console.log(ar[i]);
    i++;
}

for(let i = 0; i < ar.length; i++) { // Imprime 4 21 33 24 8
    console.log(ar[i]);
}

for (let i in ar) { // Imprime 4 21 33 24 8
    console.log(ar[i]);
}

Además de los bucles anteriores que recorren los índices del array, hay otro tipo de bucle (for..of) que permite recorrer directamente los valores del array sin necesidad del índice. Esto sería equivalente al bucle foreach de otros lenguajes de programación. También puede usarse este tipo de bucle para recorrer los caracteres de una cadena de texto (string).

let a = ["Item1", "Item2", "Item3", "Item4"];

for(let item of a) {
    console.log(item);
}

let str = "abcdefg";

for(let letter of str) {
    if(letter.match(/^[aeiou]$/)) {
        console.log(letter + " es una vocal");
    } else {
        console.log(letter + " es una consonante");
    }
}

Métodos de arrays

Al ser objetos de la clase Array, los arrays tienen una serie de métodos que podemos utlizar para realizar operaciones básicas con los mismos. Es importante diferenciar los métodos que modifican el array original de los que generan un nuevo array sin modificar el original.

Insertar y borrar al principio y al final

Para insertar y eliminar valores al principio y final del array tenemos los siguientes métodos (Todos estos métodos modifican el array original):

  • unshift: Añade uno o más valores al principio del array
  • push: Añade uno o más valores al final del array
  • shift: Elimina y devuelve el primer valor del array
  • pop: Elimina y devuelve el último valor del array
let  a = ["a"];
a.push("b", "c", "d"); // Inserta nuevos valores al final del array
console.log(a); // Imprime ["a", "b", "c", "d"]
a.unshift("A", "B", "C"); // Inserta nuevos valores al principio del array
console.log(a); // Imprime ["A", "B", "C", "a", "b", "c", "d"]

console.log(a.pop()); // Imprime y elimina la última posición → "d"
console.log(a.shift()); // Imprime y elimina la primera posición → "A"
console.log(a); // Imprime ["B", "C", "a", "b", "c"]

Convertir un array a cadena

Podemos convertir un array a string usando join() además de toString(). Por defecto, devuelve un string con todos los elementos separados por coma. Sin embargo, podemos especificar el separador a imprimir pasándoselo por parámetro.

let  a = [3, 21, 15, 61, 9];
console.log(a.join()); // Imprime "3,21,15,61,9""
console.log(a.join(" -#- ")); // Imprime "3 -#- 21 -#- 15 -#- 61 -#- 9"

Convertir una cadena a Array

Para realizar la operación inversa podemos utilizar Array.from(), que devuelve un nuevo array con los elementos de cualquier colección iterable, incluyendo un string. En este último caso, generaría un array que almacenaría en cada posición una letra de la cadena.

let s = "tenedor";
console.log(Array.from(s)); // ['t', 'e', 'n', 'e', 'd', 'o', 'r']

Concatenar arrays

Para concatenar el contenido de 2 o más arrays tenemos el método concat(). Se le pueden pasar 1 o más arrays por parámetro y su contenido se concatenará al del array principal. Es importante señalar que el array original no se modifica, sino que se genera un nuevo array que se devuelve con todos los elementos concatenados.

let  a = ["a", "b", "c"];
let  b = ["d", "e", "f"];
let  c = a.concat(b);
console.log(c); // Imprime ["a", "b", "c", "d", "e", "f"]
console.log(a); // Imprime ["a", "b", "c"] . El array original no ha sido modificado

Obtener subarrays

El método slice te devuelve un subarray. Normalmente le pasaremos 2 parámetros: la posición inicial (incluida), y la posición final (excluida). Estas posiciones pueden ser negativas (cuenta desde el final). También se puede omitir la posición final si queremos obtener hasta el final del array. Cabe destacar que este método no modifica el array original.

let a = ["a", "b", "c", "d", "e", "f"];
let b = a.slice(1, 3); // (posición de inicio → incluida, posición final → excluida)
console.log(b); // Imprime ["b", "c"]
console.log(a); // Imprime ["a", "b", "c", "d", "e", "f"]. El array original se modifica
console.log(a.slice(3)); // Un parámetro. Devuelve desde la posición 3 al final → ["d", "e", "f"] 

Insertar y borrar en posiciones intermedias

Para añadir y eliminar elementos en posiciones intermedias del array, tenemos el método splice. A este método le pasamos por parámetro la posición en la que queremos trabajar, el número de elementos a borrar a partir de ahí (pueden ser 0), y los elementos que insertaremos en su lugar (opcional). Es importante saber que este método modifica el array original. Si queremos que nos devuelva el resultado como un nuevo array sin modificar el original, podemos usar el método equivalente toSpliced (estándar a partir de la versión ES2023).

let  a = ["a", "b", "c", "d", "e", "f"];
a.splice(1, 3); // Elimina 3 elementos desde la posición 1 ("b", "c", "d")
console.log(a); // Imprime ["a", "e", "f"]
a.splice(1,1, "g", "h"); // Elimina 1 elemento en la posición 1 ("e"), e inserta "g", "h" en esa posición
console.log(a); // Imprime ["a", "g", "h", "f"]
a.splice(3, 0, "i"); // En la posición 3, no elimina nada, e inserta "i"
console.log(a); // Imprime ["a", "g", "h", "i", "f"]

let a2 = a.toSpliced(2, 1, "H");
console.log(a); // ["a", "g", "h", "i", "f"] -> No modificado
console.log(a2); // ["a", "g", "H", "i", "f"]

Invertir las posiciones del array

Podemos invertir el orden del array usando el método reverse. Esto modifica el array original. Si por el contrario, queremos devolver una versión invertida del array sin modificar el original, tenemos el nuevo método (ES2023) toReversed.

let  a = ["a", "b", "c", "d", "e", "f"];
console.log(a.toReversed()); // ["f", "e", "d", "c", "b", "a"]
console.log(a); // ["a", "b", "c", "d", "e", "f"] -> Array original no modificado

a.reverse(); // Invierte el orden del array original
console.log(a); // ["f", "e", "d", "c", "b", "a"]

Ordenar un array

También, podemos ordenar los elementos de un array usando el método sort. Al igual que ocurría con reverse, este método modifica el array original. En la versión ES2023 se ha introducido el método toSorted, que devuelve un nuevo array ordenado sin modificar el original.

let  a = ["Peter", "Anne", "Thomas", "Jen", "Rob", "Alison"];
console.log(a.toSorted()); // ["Alison", "Anne", "Jen", "Peter", "Rob", "Thomas"]
console.log(a); // ["Peter", "Anne", "Thomas", "Jen", "Rob", "Alison"] -> Original no modificado

a.sort(); // Ordena el array original
console.log(a); // ["Alison", "Anne", "Jen", "Peter", "Rob", "Thomas"]

Importante: Por defecto todos los valores de un array se ordenan alfabéticamente. Es decir, si un valor no es de tipo string, se convierte a dicho tipo internamente de cara a saber qué posición ocupará.

Si queremos ordenar otro tipo de datos, o de una manera diferente al orden alfabético de la 'a' a la 'z', debemos pasarle al método sort (o sorted) una función de ordenación. Esta función comparará 2 valores del array y devolverá un valor numérico indicando cual es menor (negativo si el primero es menor, 0 si son iguales y positivo si el primero es mayor).

let a = [20, 6, 100, 51, 28, 9];
a.sort(); // Ordena el array alfabéticamente
console.log(a); // Imprime [100, 20, 28, 51, 6, 9]
a.sort((n1, n2) => n1 - n2); // Trabajando con números, podemos devolver la resta
console.log(a); // Imprime [6, 9, 20, 28, 51, 100]

/**** Ejemplo con objetos, que veremos más adelante ****/
class Person {
  constructor(name, age) {
    this.name = name;
    this.age = age;
  }

  toString() { 
    return this.name + " (" + this.age + ")";
  }
}

let people = [
  new Person("Thomas", 24),
  new Person("Mary", 15),
  new Person("John", 51),
  new Person("Phillip", 9)
];

people.sort((p1, p2) => p1.age - p2.age); // Ordenamos por edad número
console.log(people.toString()); // "Phillip (9),Mary (15),Thomas (24),John (51)"

people.sort((p1, p2) => p1.name.localeCompare(p2.name)); // Ordenamos por nombre (string)
console.log(people.toString()); // "John (51),Mary (15),Phillip (9),Thomas (24)"

Cambiar el valor de una posición generando un nuevo array

Otro método nuevo de la versión ES2023 es with. Este nos devuelve un nuevo array con un valor cualquiera modificado. Al contrario que asignar un nuevo valor a una posición del array usando la notación de corchetes, este método preserva el array original intacto.

let  a = [1, 2, 3, 4];
let a2 = a.with(2, 99); // En lugar de hacer a[2] = 99
console.log(a); // [1, 2, 3, 4] -> Original
console.log(a2); // [1, 2, 99, 4] -> Nuevo array con el cambio

Recorrer array de manera funcional

Podemos iterar por los elementos de un array usando el método forEach. De forma opcional, podemos llevar un seguimiento del índice al que está accediendo en cada momento, e incluso recibir el array como tercer parámetro.

let a = [3, 21, 15, 61, 9, 54];

a.forEach((num, indice, array) => { // índice y array son parámetros opcionales
    console.log(indice + " -> " + num);
});

Comprobar una condición con todos los elementos

El método every devolverá un booleano indicando si todos los elementos del array cumplen cierta condición. Esta función recibirá un elemento, lo testeará, y devolverá cierto o falso dependiendo de si cumple la condición o no. A este tipo de funciones se les llama predicados.

Por otro lado, el método some es similar, pero devuelve true en cuanto uno de los elementos del array cumpla la condición.

let a = [3, 21, 15, 61, 9, 54];
console.log(a.every(num =>  num < 100));  // Comprueba si cada número es menor a 100. Imprime true
console.log(a.every(num => num % 2 == 0)); // Comprueba si cada número es par. Imprime false

console.log(a.some(num => num % 2 == 0));  // Comprueba si algún elemento del array es par. Imprime true

Modificar todos los elementos del array (map)

Para modificar todos los elementos de un array, el método map recibe una función que devuelve un nuevo valor a partir de un elemento del array. El método map devolverá al final un nuevo array del mismo tamaño conteniendo todos los valores generados.

let  a = [4, 21, 33, 12, 9, 54];
console.log(a.map(num => num*2)); // Imprime [8, 42, 66, 24, 18, 108]

Filtrar los elementos del array (filter)

Para filtrar los elementos de un array, y obtener como resultado un array que contenga sólo los elementos que cumplan cierta condición, usamos el método filter. Este método recibe una función (predicado), que devuelve un valor booleano, indicando los elementos que se incluirán en el nuevo array generado.

let  a = [4, 21, 33, 12, 9, 54];
console.log(a.filter(num => num % 2 == 0));  // Imprime [4, 12, 54]

Calcular valor a partir de array (reduce)

El método reduce usa una función que acumula un valor, procesando cada elemento (segundo parámetro) con el valor acumulado (primer parámetro). Como segundo parámetro de reduce, deberías pasar un valor inicial. Si no pasas un valor inicial, el primer elemento de un array será usado como tal (si el array está vacío devolvería undefined).

Para hacer lo mismo que reduce pero al revés (empezando por el final del array), usaremos reduceRight.

let  a = [4, 21, 33, 12, 9, 54];
console.log(a.reduce((total, num) => total + num, 0));  // Suma todos los elementos del array. Imprime 133
console.log(a.reduce((max, num) => Math.max(max, num), 0));  // Número máximo del array. Imprime 54

console.log(a.reduceRight((total, num)  => total - num)); // Imprime -25 (Toma la última posición como valor inicial al no proporcionarlo)

Buscar elementos en un array

Usando indexOf, podemos conocer si el valor que le pasamos se encuentra en el array o no. Si lo encuentra nos devuelve la primera posición donde está, y si no, nos devuelve -1. Si usamos el método lastIndexOf nos devuelve la primera ocurrencia encontrada empezando desde el final.

De forma opcional, podemos pasarle como segundo parámetro la posición del array a partir de la cual buscará.

let a = [3, 21, 15, 61, 9, 15];
console.log(a.indexOf(15)); // Imprime 2
console.log(a.indexOf(15, 3)); // Imprime 5
console.log(a.indexOf(56)); // Imprime -1. No encontrado
console.log(a.lastIndexOf(15)); // Imprime 5	

Buscar elementos con métodos funcionales

find Encuentra y devuelve el primer valor que encuentre que cumple la condición que se establece. Con findIndex, devolvemos la posición que ocupa ese valor en el array.

Recientemente se han estándarizado métodos equivalentes que empiezan a buscar desde la última posición del array. Estos métodos son findLast y findLastIndex.

let numbers = [2, 4, 6, 9, 14, 16];
console.log(numbers.find(num => num >= 10)); // Imprime 14 (primer valor encontrado >= 10)
console.log(numbers.findIndex(num => num >= 10)); // Imprime 4 (numbers[4] -> 14)
console.log(numbers.findLast(num => num >= 10)); // Imprime 16 (último valor encontrado >= 10)
console.log(numbers.findLastIndex(num => num >= 10)); // Imprime 5 (numbers[5] -> 16)

Extraer arrays internos

A partir de un array que contiene otros arrays. El método flat extrae los elementos de los arrays internos y los concatena en el array resultante. Se le puede pasar por parámetro hasta que nivel de arrays internos debe extraer (por defecto es 1).

let nums = [[1, 2, 3], [4, 5, 6]];
console.log(nums.flat()); // [1, 2, 3, 4, 5, 6]

let nums2 = [[[1, 2],[3,4]], [5,6], [[7, 8], 9]];
console.log(nums2.flat()); // [[1, 2], [3, 4], 5, 6, [7, 8], 9]
console.log(nums2.flat(2)); // [1, 2, 3, 4, 5, 6, 7, 8, 9]

El método flatmap es una combinación de map y flat. Si la función devuelve un array, los valores se extraen y se incluyen como parte del array final resultante.

let words = ["house", "tree", "dog"];
console.log(words.map(w => Array.from(w))); // [["h", "o", "u", "s", "e"], ["t", "r", "e", "e"], ["d", "o", "g"]]
console.log(words.flatMap(w => Array.from(w))); // ["h", "o", "u", "s", "e", "t", "r", "e", "e", "d", "o", "g"]

<< Colecciones de datos Rest/Spread >>