Introducción a las colecciones en JavaScript

En esta guía, comenzaremos con Colecciones en JavaScript para principiantes, con ejemplos prácticos y explicaciones de Colecciones indexadas, Colecciones con clave y Colecciones HTML DOM.

Introducción

Los grupos de datos en diferentes formas son una de las estructuras de datos fundamentales en la mayoría de los lenguajes de programación. En muchos casos, los grupos de datos expresados ​​a través de diferentes tipos de datos se denominan Colecciones.

En esta guía, veremos Colecciones en JavaScript y cuándo usar qué tipo de colección. Los tres Grupos de colecciones principales que veremos son:

  • Colecciones indexadas
  • Colecciones con clave
  • Colecciones DOM

Colecciones indexadas

Una colección indexada es una colección de datos listados por su índice. Los índices de colección de JavaScript están basados ​​en 0, lo que significa que comienzan en 0, no en 1 y van hasta n-1, siendo n el número de objetos en la colección. JavaScript tiene dos tipos de colecciones indexadas Arrays y TypedArrays.

Objeto de matriz

Un objeto Array en JavaScript es una lista ordenada, a cuyos elementos se puede acceder mediante índices. Hay múltiples formas de crear un objeto Array en JavaScript, y no hay mucha diferencia bajo el capó:

1
2
3
let myArray1 = [x1, x2, ... , xN];
let myArray2 = new Array(x1, x2, ... , xN);
let myArray3 = Array(x1, x2, ... , xN);

Las matrices en JavaScript no se basan en tipos, lo que significa que no tiene que definir el tipo de la matriz de antemano y no tiene que agregar solo elementos homogéneos:

1
let myArray = ["one", 1, "two", 2];

Esta es una sintaxis completamente válida, y la matriz se complace en almacenar referencias tanto a cadenas como a enteros. Volvamos a aclarar rápidamente qué índices de una matriz son realmente:

1
2
let myArray = ["one", 1, "two", 2];
//                0    1    2    3    --> index values

Entonces, comenzando desde 0 hasta n-1, donde n es la longitud de la matriz.

Cada matriz tiene una propiedad llamada longitud. La longitud de una matriz se determina al mismo tiempo que se inicializa la matriz. Entonces, cuando creamos una matriz, se asigna un valor a su propiedad longitud.

Si console.log(myArray.length), la salida sería 4.

Además, cambiar la longitud cambiará la matriz. Puede truncar fácilmente una matriz acortando su longitud y expandirla alargándola:

1
2
3
4
5
6
7
8
let myArray = ["one", 1, "two", 2];
console.log(myArray);

myArray.length = 5;
console.log(myArray);

myArray.length = 1;
console.log(myArray);

Esto resulta en:

1
2
3
["one", 1, "two", 2]
["one", 1, "two", 2, undefined]
["one"]

En JavaScript, puede crear una matriz sin ningún elemento, pero de cierta longitud. Puede pensar en esto como algo así como asignar (reservar) memoria por adelantado. Esto es exactamente lo que ocurre cuando expandimos una matriz cambiando su “longitud” para que sea mayor que antes.

Dado que hay tres formas de crear una matriz llena de elementos, también hay tres formas de crear matrices vacías con memoria asignada, así:

1
2
3
4
5
6
7
8
9
let myArray1 = new Array(4); // creates an array with 4 empty spaces
console.log(myArray1.length); // 4

let myArray2 = Array(4); // similar to the previous one, just without the keyword new
console.log(myArray2.length); // 4

let myArray3 = [];
myArray3.length = 4 // this one is a bit different, we assign the value to the property length
console.log(myArray3.length); // 4

Hasta ahora, no había diferencia entre estas tres formas de crear una matriz, además de variaciones en la sintaxis.

Sin embargo, si desea crear una matriz con un solo elemento que sea un “Número”, tendrá que usar corchetes y definir los elementos concretos, en lugar del tamaño de una matriz.

Esto se debe a que si pasa un número a un constructor Array, creará una matriz vacía y asignará esa cantidad de espacios.

1
2
3
4
// New array with 10 spaces
let myArray1 = new Array(10)
// New array with a single element
let myArray3 = [10]

Adición de elementos a una matriz

Hemos visto cómo crear un Array, ya sea vacío o no vacío. Ahora veamos cómo agregarle nuevos elementos. Como estamos trabajando con colecciones indexadas, estaremos operando con índices.

Como ya habíamos creado un Array de 4 elementos vacíos, trabajemos con eso. Para agregar un elemento, todo lo que tenemos que hacer es acceder al elemento a través de su índice y asignarle un valor:

1
2
3
4
5
6
7
8
let myArray1 = new Array(4)

myArray1[0] = "one"
myArray1[1] = "two"
myArray1[2] = "three"
myArray1[3] = "four"

console.log(myArray)

Esta será la salida:

1
['one', 'two', 'three', 'four']

A pesar de que asignamos 4 espacios para los elementos al crear una matriz, en JavaScript, las ‘Array’ se crean dinámicamente, lo que significa que puede reducirlas o expandirlas en cualquier momento.

Esto significa que podemos agregar más elementos a nuestro Array, aunque lo "bordeamos" con 4 espacios:

1
2
3
4
myArray1[4] = "five"
myArray1[5] = "six"

console.log(myArray) // Output: ['one', 'two', 'three', 'four', 'five', 'six']

Podemos iterar fácilmente a través de una matriz usando un bucle for o un bucle forEach:

1
2
3
4
5
6
7
console.log('Traditional for loop:')
for (let i = 0; i < myArray1.length ; i++) {
    console.log(myArray1[i]);
}

console.log('Functional forEach loop:')
myArray1.forEach( function (element){ console.log(element);});

Esto generará:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
Traditional for loop:
one
two
three
four
five
six

Functional forEach loop:
one
two
three
four
five
six

Métodos de matriz

Ahora que le hemos cogido el truco a las cosas, experimentemos con los métodos Array integrados en JavaScript. Ya has visto uno en el ejemplo anterior: un bucle .forEach() que se llama en myArray1.

Repasemos los más utilizados:

  • push() - agrega un elemento al final de una matriz
1
2
3
let myArray = [1,2,3];
myArray.push(4);
console.log(myArray); // outputs [1, 2, 3, 4]
  • pop() - elimina el último elemento de una matriz
1
2
3
let myArray = [1,2,3,4];
myArray.pop();
console.log(myArray); // outputs [1, 2, 3]
  • concat() - une arreglos (dos o más) en un solo arreglo
1
2
3
4
5
6
7
8
9
// Concating 2 arrayslet myArray1 = [1,2,3]
let myArray2 = [4,5,6];
let finalArray1 = myArray1.concat(myArray2);
console.log(finalArray1); // [1,2,3,4,5,6]
    
// Concating 3 arrayslet 
myArray3 = [7,8,9];
let finalArray2 = myArray1.concat(myArray2, myArray3);
console.log(finalArray2); // [1,2,3,4,5,6,7,8,9]
  • join(delimiter) - une todos los elementos en una cadena, delimitada con un delimiter
1
2
3
4
5
let myArray = ["Earth", "Wind", "Fire"];
let arrayString = myArray.join(",");
console.log(arrayString); // outputs Earth, Wind, Fire
// Bonus example
console.log(arrayString + "- September"); // outputs Earth, Wind, Fire - September
  • reverse() - exactamente eso, invierte el orden de los elementos en la matriz
1
2
3
let myArray = [1,2,3];
let reversed = myArray.reverse();
console.log(reversed); // [3,2,1]
  • slice(start, end) - copia una parte de una matriz desde el índice start hasta el índice end-1
1
2
3
let myArray = [1,2,3,4,5,6];
myArray = myArray.slice(3, 5);
console.log(myArray); // [4,5]

TypedArray Objeto

Los objetos Array son perfectos para trabajar con cualquier tipo de datos en JavaScript, ya que pueden almacenar diferentes tipos de elementos en una matriz y tienen métodos poderosos para manipular esos elementos.

Sin embargo, cuando es necesario trabajar con datos binarios sin procesar, es cuando entran en juego los objetos TypedArray. Los datos sin procesar se procesan al manipular audio y video, por ejemplo.

Arquitectura de un objeto TypedArray

Las matrices escritas en JavaScript se dividen en búferes y vistas. Un búfer es un objeto que solo almacena una parte de los datos, sin métodos para acceder o manipular esos datos. Para lograrlo, debe usar una vista, que proporciona un contexto, un tipo de datos que convierte los datos en un TypedArray.

Un búfer se implementa a través de un objeto ArrayBuffer. Se utiliza para representar un búfer de datos binarios de longitud fija. Para representar este búfer, tenemos que crear una vista, DataView, que represente ese búfer en un formato elegido. Hay varios tipos de vistas, que representan los tipos numéricos más comunes:

  • Int8Array - rango de valores [-128, 127]
  • UInt8Array - rango de valores [0, 255], u significa sin firmar
  • Int16Array - rango de valores [-32768, 32767]
  • UInt16Array - rango de valores [0, 65535]
  • Float32Array - rango de valores [1.2E-38, 3.4E38]

Creando un TypedArray

Al crear un objeto TypedArray de cierto tipo, logramos lo que mencionamos anteriormente: crear un búfer y una vista. No hay un constructor explícito para el objeto TypedArray - no hay una sintaxis nueva TypedArray() - instanciamos directamente el tipo de matriz que necesitamos:

1
let tArray = new Int8Array(8);

Aquí, hemos creado un búfer y una vista para un Int8Array con un tamaño de 8 bytes. La asignación de valores a los elementos es la misma que para el objeto Array:

1
2
tArray[0] = 10;
console.log(tArray);

Esto generará:

1
Int8Array [ 10, 0, 0, 0, 0, 0, 0, 0 ]

De esta manera, podemos llenar el TypedArray con valores que normalmente están presentes, pero no limitados a, al procesar audio o video, pero ese es un tema para un artículo completamente nuevo.

Colecciones con clave

Una colección con clave es una colección de datos representados en la notación clave-valor. Los valores de los elementos se acceden y manipulan a través de sus respectivas claves.

En JavaScript, hay dos tipos de colecciones con clave: Map y Set.

Tanto Maps como Sets en JavaScript pueden tener un solo valor atribuido a una sola clave, aunque podría piratearlo atribuyéndole una Lista como valor, que contiene varios elementos. Todavía vale la pena señalar que la ‘Lista’ en sí misma es el valor, no sus elementos constituyentes.

Además, las claves deben ser únicas.

El objeto del mapa

Un objeto Map en JavaScript es un mapa estándar que contiene pares clave-valor. Para crear un nuevo objeto Map, simplemente llamamos al constructor:

1
let myMap = new Map();

Adición de un elemento a un mapa

Un mapa vacío no nos servirá de mucho. Agreguemos algunos elementos a través del método set(), que acepta un nombre_clave que tiene que ser una Cadena, y un valor que puede ser de cualquier tipo:

1
2
3
4
myMap.set("one", 1);
myMap.set("two", 2);
myMap.set("three", "three");
console.log(myMap);

Los mapas también son heterogéneos, por lo que no es necesario tener el mismo tipo de valor para todas las claves:

1
Map { 'one' => 1, 'two' => 2, 'three' => 'three' }

Acceso a elementos de un mapa

Para acceder a los elementos de un Mapa, simplemente los ‘obtenemos()’, pasando el ’nombre_clave’ ya que estos son los identificadores únicos en el Mapa:

1
console.log(myMap.get("two")); // Output: 2

Dado que esta colección no está basada en índices, no podemos acceder a algún valor usando corchetes: myMap["two"] devolverá un valor indefinido.

Sin embargo, si llamamos al método get(key_name) en la clave inexistente, el valor devuelto también será indefinido.

Métodos de mapas

Los dos métodos principales que usará con los mapas son get() y set(), pero también querrá iterar sobre ellos. La clase Map también tiene un forEach() que se puede usar fácilmente para iterar y realizar operaciones en todas las entradas. Lo cubriremos en un momento.

Además de forEach(), estos son los métodos más utilizados en Maps:

  • set(key_name, value) - añade un par clave-valor al Mapa.

  • get(key_name) - devuelve el valor asignado a la clave pasada, si no existe tal clave - devuelve indefinido.

  • has(key_name) - devuelve true o false dependiendo de si un Map tiene una clave key_name o no:

1
2
console.log(myMap.has("two")); // true
console.log(myMap.has("five")) // false
  • delete(key_name): elimina tanto la clave como el valor de acuerdo con el key_name pasado, si se pasa una clave que no existe, no pasa nada:
1
2
3
4
myMap.delete("two")console.log(myMap);  
// Output: Map { 'one' => 1, 'three' => 'three' }
myMap.delete("five")console.log(myMap); 
// Output: Map { 'one' => 1, 'three' => 'three' }
  • clear() - elimina todos los pares clave-valor del objeto Map:
1
2
3
myMap.clear();
console.log(myMap); 
// Output: Map {}

Hay una propiedad principal del ‘Mapa’: es la propiedad ’tamaño’. Contiene un valor numérico que representa el tamaño de un objeto Mapa:

1
2
3
4
5
let myMap = new Map();
myMap.set("one", 1);
myMap.set("two", 2);
console.log(myMap.size); 
// Output: 2

Iterando a través de un mapa {#iterando a través de un mapa}

Iterar a través de un objeto Map en JavaScript es un poco al estilo de Python. Podemos usar la sintaxis for..of para lograr esto:

1
2
3
for (let [k, v] of myMap){    
  console.log(k + " written in number is " + v)
}

Para cada entrada, con el par clave-valor ([k, v]) de myMap, haga ...:

1
2
one written in number is 1
two written in number is 2

O podemos utilizar el método forEach() más funcional:

1
myMap.forEach(function(value) { console.log(value);});

Lo que resulta en:

1
2
3
1
2
three

O bien, puede recuperar tanto el valor como la clave:

1
myMap.forEach(function(value, key) { console.log(value, key);});

Lo que resulta en:

1
2
3
1 one
2 two
three three

Mapa sobre objeto

Dado que ann Object en JavaScript también sigue la notación clave-valor, puede ser difícil decidir cuál usar y cuándo usarlo.

Hay algunos consejos sobre el uso de estos dos:

  • Mapas se debe usar cuando las claves son desconocidas hasta el tiempo de ejecución o cuando todas las claves son del mismo tipo y todos los valores son del mismo tipo.
  • Objetos debe usarse cuando hay una lógica que opera en elementos individuales, en lugar de en una colección de elementos.

El objeto WeakMap

Un objeto WeakMap en JavaScript es una colección de pares clave-valor, donde las claves son objetos únicamente y los valores pueden ser de varios tipos. El nombre débil proviene de la actividad en la que estos objetos son el objetivo de la recolección de basura, lo que significa que si no hay referencias a ellos, se eliminarán.

La API para WeakMap es la misma que la API de Map, sin ningún método de iteración ya que los mapas débiles no son iterables:

1
2
3
4
5
6
let myMap = new WeakMap();

let athlete = class Athlete{}
myMap.set(athlete, 1);

console.log(myMap.get(athlete))

Esto resulta en:

1
1

El Objeto Conjunto

Un objeto Set en JavaScript es solo una colección de valores. Estos valores son únicos, lo que significa que no se permiten duplicados y tratar de agregar un elemento duplicado simplemente no agregará nada.

También podemos probar esto ya que los conjuntos de impresión imprimen sus elementos en orden de inserción, y agregar un elemento duplicado al principio y al final solo dará como resultado que el primero esté presente.

Crear un Set es tan simple como llamar a su constructor:

1
let mySet = new Set();

Adición de un elemento a un conjunto

Para agregar un nuevo elemento a un conjunto, usamos el método add(value).

Los conjuntos pueden contener valores arbitrarios. Intentemos agregar algunos elementos y agregar deliberadamente duplicados, para ver cómo se comporta un ‘Conjunto’:

1
2
3
4
5
6
mySet.add(1);
mySet.add("one");
mySet.add("one");
mySet.add("two");
mySet.add(1);
console.log(mySet);

Los conjuntos mantienen el orden de inserción, por lo que podemos probar fácilmente si el nuevo ‘1’ reemplaza al antiguo ‘1’ o si simplemente se omite su adición:

1
Set { 1, 'one', 'two' }

El ‘Conjunto’ reconoce los mismos elementos de valor y guarda solo una copia de cada uno. Los conjuntos son excelentes para filtrar valores duplicados: puede colocar un montón de valores que se supone que son únicos y se filtrarán.

Sin embargo, si no necesita un ‘Conjunto’ al final, es mejor filtrar una colección más adecuada.

Establecer métodos

Los métodos de conjunto son bastante similares a los métodos de mapa, y puede agregar y eliminar valores fácilmente, así como verificar si algunos pertenecen al conjunto o borrarlo:

  • add(value) - agrega un nuevo valor al objeto Set
  • delete(value) - elimina el value pasado del objeto Set
  • has(value) - devuelve true o false dependiendo de si el value está en el objeto Set
  • clear() - borra todos los valores del objeto Set
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
let mySet = new Set()

// Add values
mySet.add(1);
mySet.add("two");

// Delete a value
mySet.delete("two")
// Check if the deleted value is present
console.log(mySet.has("two")) // false
// Clear all values
mySet.clear()
// Check if first value is present
console.log(mySet.has(1)) // false

Objeto de conjunto débil {#objeto de conjunto débil}

Un objeto WeakSet es una colección de objetos. Igual que los valores de Set, los objetos de WeakSet tienen que ser únicos. Esto se refiere a los objetos en la memoria, no a sus campos o valores.

Hay algunas diferencias clave entre un ‘Conjunto’ y un ‘Conjunto débil’:

  • WeakSet es una colección de objetos, mientras que un Set es una colección de valores de cualquier tipo.
  • Igual que WeakMap, si no hay ninguna referencia al objeto WeakSet, se elimina.

Colecciones HTML DOM

Este tipo de colección está relacionada con el desarrollo web front-end.

Cuando trabajamos en una página web, podemos acceder a todos los elementos de la página gracias al árbol DOM. Por lo tanto, al acceder a varios elementos a la vez, se devuelven como una HTMLCollection, una colección de elementos HTML similar a una matriz.

Si tenemos una página web que contiene varias etiquetas <p>, podemos recuperarlas con document.getElementsByTagName("p"), que devuelve una colección de todos los elementos <p> de la página:

1
2
let myHTMLCollection = document.getElementsByTagName("p");
console.log(myHTMLCollection[1]);

Ahora podemos reconocer que una HTMLCollection es una colección "indexada", ya que estamos accediendo a un elemento de ella usando un valor de índice. No es una colección de JavaScript indexada verdadera ya que no es una matriz, porque no tiene los métodos de matriz, pero el acceso al índice está disponible.

Una HTMLCollection tiene la propiedad longitud, que devuelve su tamaño.

Conclusión

Dependiendo de los datos con los que esté trabajando, decidirá si usar Colecciones indexadas o Colecciones con clave. Si trabaja en una página web, probablemente encontrará HTMLCollections.

Como resumen rápido:

  • Colecciones indexadas:
    • Elements are based on index values - in JavaScript starting from 0.
    • Array object and TypedArray object.
  • Colecciones con llave:
    • Elements are based on key-value pairs (JSON-like).
    • Map object and Set object.
  • Colecciones HTML DOM:
    • Elements are HTML elements, based on index values, again starting from 0.