Tutorial

Información sobre los objetos Map y Set en JavaScript

DevelopmentJavaScript

El autor seleccionó a Open Internet/Free Speech Fund para recibir una donación como parte del programa Write for DOnations.

En el ámbito de JavaScript, a menudo los desarrolladores pasan mucho tiempo decidiendo la estructura de datos correcta que se usará. Esto se debe a que elegir la estructura de datos correcta puede facilitar la manipulación de esos datos posteriormente, con lo cual se puede ahorrar tiempo y facilitar la comprensión del código. Las dos estructuras de datos predominantes para almacenar conjuntos de datos son los Object y Array (un tipo de objeto). Los desarrolladores utilizan Object para almacenar pares clave-valor y Array para almacenar listas indexadas. Sin embargo, para dar más flexibilidad a los desarrolladores, en la especificación ECMAScript 2015 se introdujeron dos nuevos tipos de objetos iterables: los Map, que son grupos ordenados de pares clave-valor, y los Set, que son grupos de valores únicos.

En este artículo, repasará los objetos Map y Set, veremos qué los hace similares o diferentes de los Object y Array, las propiedades y los métodos disponibles para ellos y ejemplos de algunos usos prácticos.

Map

Un Map es un grupo de pares clave-valor en el que se puede usar cualquier tipo de datos como clave y que puede mantener el orden de sus entradas. En los Map se incluyen elementos tanto de Object (un grupo único de pares clave-valor) como de Array (un grupo ordenado), pero conceptualmente son más parecidos a los Object. Esto se debe a que, si bien el tamaño y el orden de las entradas se mantiene como en un Array, las propias entradas son pares clave-valor como los Object.

Los Map se pueden inicializar con la sintaxis new Map():

const map = new Map()

Esto nos proporciona un Map vacío:

Output
Map(0) {}

Añadir valores a un Map

Puede añadir valores a un mapa con el método set(). En el primer argumento se representará la clave y en el segundo el valor.

A continuación, se añaden tres pares clave-valor a map:

map.set('firstName', 'Luke')
map.set('lastName', 'Skywalker')
map.set('occupation', 'Jedi Knight')

Aquí, empezamos a ver cómo en los Map se encuentran elementos tanto de Object como de Array. Como en el caso de un Array, tenemos un grupo con índice cero y también podemos ver la cantidad de elementos disponibles por defecto en el Map. En los Map se utiliza la sintaxis => para indicar los pares clave-valor como key => value:

Output
Map(3) 0: {"firstName" => "Luke"} 1: {"lastName" => "Skywalker"} 2: {"occupation" => "Jedi Knight"}

Este ejemplo se parece a un objeto normal con claves basadas en cadenas, pero con los Map podemos usar cualquier tipo de datos como clave.

Además de establecer los valores manualmente en un Map, también podemos inicializarlo con estos ya incluidos. Esto se hace utilizando un Array de Array, con dos elementos que son cada uno pares clave-valor, que tiene el siguiente aspecto:

[ [ 'key1', 'value1'], ['key2', 'value2'] ]

Usando la siguiente sintaxis, podemos volver a crear el mismo Map:

const map = new Map([
  ['firstName', 'Luke'],
  ['lastName', 'Skywalker'],
  ['occupation', 'Jedi Knight'],
])

Nota: En este ejemplo se utilizan las comas al final, también conocidas como comas pendientes. Esta es una práctica de formato de JavaScript en la cual, cuando se declara un grupo de datos, el elemento final de una serie tiene una coma al final. Aunque esta opción de formato puede utilizarse para lograr diferenciales más limpios y facilitar la manipulación del código, su uso está atado a la preferencia. Para obtener más información sobre las comas al final, consulte este artículo de Comas al final de los documentos web de MDN.

De hecho, esta sintaxis es la misma que la del resultado de la invocación de Object.entries() en un Object. Esto ofrece una alternativa ya creada para convertir un Object en un Map, como se muestra en el siguiente bloque de código:

const luke = {
  firstName: 'Luke',
  lastName: 'Skywalker',
  occupation: 'Jedi Knight',
}

const map = new Map(Object.entries(luke))

También puede convertir un Map de nuevo en un Object o Array con una sola línea de código.

Con lo siguiente convertiremos un Map en un Object:

const obj = Object.fromEntries(map)

Esto dará como resultado el siguiente valor de obj:

Output
{firstName: "Luke", lastName: "Skywalker", occupation: "Jedi Knight"}

Ahora, convertiremos un Map en un Array:

const arr = Array.from(map)

Esto dará como resultado el siguiente Array para arr:

Output
[ ['firstName', 'Luke'], ['lastName', 'Skywalker'], ['occupation', 'Jedi Knight'] ]

Claves de Map

En los Map se acepta cualquier tipo de datos como clave y no se permiten valores de clave duplicados. Podemos demostrarlo creando un mapa y usando valores que no sean de cadena como claves y estableciendo dos valores para la misma clave.

Primero, iniciaremos un mapa con claves que no sean de cadena:

const map = new Map()

map.set('1', 'String one')
map.set(1, 'This will be overwritten')
map.set(1, 'Number one')
map.set(true, 'A Boolean')

En este ejemplo, se anulará la primera clave de 1 con la siguiente y se tratarán la cadena '1'​​​ y el número 1 como claves únicas:

Output
0: {"1" => "String one"} 1: {1 => "Number one"} 2: {true => "A Boolean"}

Aunque comúnmente se cree que un Object normal de JavaScript puede usar números de antemano, booleanos y otros tipos de datos primitivos como claves, este realmente no es el caso debido a que los Object cambian todas las claves por cadenas.

A modo de ejemplo, inicialice un objeto con una clave numérica y compare el valor para una clave numérica 1 y una clave "1" convertida a cadena.

// Initialize an object with a numerical key
const obj = { 1: 'One' }

// The key is actually a string
obj[1] === obj['1']  // true

Es por esto que si intenta usar un Object como clave se imprimirá la cadena object Object en su lugar.

Como ejemplo, cree un Object y luego utilícelo como clave de otro Object:

// Create an object
const objAsKey = { foo: 'bar' }

// Use this object as the key of another object
const obj = {
  [objAsKey]: 'What will happen?'
}

Obtendrá el siguiente resultado:

Output
{[object Object]: "What will happen?"}

Este no es el caso de Map. Intente crear un Object y establecerlo como clave de un Map:

// Create an object
const objAsKey = { foo: 'bar' }

const map = new Map()

// Set this object as the key of a Map
map.set(objAsKey, 'What will happen?')

La key del elemento del Map es ahora el objeto que creamos.

Output
key: {foo: "bar"} value: "What will happen?"

Hay algo importante que se debe considerar respecto del uso de un Object o Array como clave: en el Map se utiliza la referencia al Object para comparar la igualdad y no el valor literal del Object. En JavaScript {} == {} muestra false, porque los dos Object no son los mismos, a pesar de tener el mismo valor (vacío).

Esto significa que al añadir dos Object únicos con el mismo valor se creará un Map con dos entradas:

// Add two unique but similar objects as keys to a Map
map.set({}, 'One')
map.set({}, 'Two')

Obtendrá el siguiente resultado:

Output
Map(2) {{…} => "One", {…} => "Two"}

Sin embargo, si se usa la misma referencia de Object dos veces se creará un Map con una entrada.

// Add the same exact object twice as keys to a Map
const obj = {}

map.set(obj, 'One')
map.set(obj, 'Two')

Esto dará como resultado lo siguiente:

Output
Map(1) {{…} => "Two"}

El segundo set() actualiza exactamente la misma clave que la primera, por lo que finalmente obtenemos un Map que solo tiene un valor.

Obtener y eliminar elementos de un Map

Una de las desventajas de trabajar con Object es que puede resultar difícil enumerarlos, así como trabajar con las claves o los valores. En la estructura de Map, en contraste, se incluyen muchas propiedades integradas. Esto hace que pueda trabajar de forma más directa con sus elementos.

Podemos inicializar un nuevo Map para mostrar los métodos y las propiedades siguientes: delete(), has(), get() y size.

// Initialize a new Map
const map = new Map([
  ['animal', 'otter'],
  ['shape', 'triangle'],
  ['city', 'New York'],
  ['country', 'Bulgaria'],
])

Utilice el método has() para verificar la existencia de un elemento en un mapa. has() mostrará un booleano.

// Check if a key exists in a Map
map.has('shark') // false
map.has('country') // true

Utilice el método get() para obtener un valor por clave.

// Get an item from a Map
map.get('animal') // "otter"

Un beneficio especial de los Map respecto los Object es que pueden encontrar el tamaño de un Map en cualquier momento, como con un Array. Puede obtener el recuento de elementos en un Map con la propiedad size. Para esto se requieren menos pasos que al convertir un Object en un Array para encontrar la longitud.

// Get the count of items in a Map
map.size // 4

Utilice el método delete() para eliminar un elemento de un Map por clave. En el método, se mostrará un booleano (true si existió un elemento y se eliminó, y false si no coincidió con ningún elemento).

// Delete an item from a Map by key
map.delete('city') // true

Esto dará como resultado el siguiente Map:

Output
Map(3) {"animal" => "otter", "shape" => "triangle", "country" => "Bulgaria"}

Por último, se pueden eliminar todos los valores de un Map con map.clear().

// Empty a Map
map.clear()

Verá el siguiente resultado:

Output
Map(0) {}

Claves, valores y entradas para Map

Los Object pueden recuperar claves, valores y entradas usando las propiedades del constructor Object. Por otro lado, en los Map se incluyen los métodos de prototipo que nos permiten obtener directamente las claves, los valores y las entradas de la instancia del Map.

En los métodos keys(), values() y entries() se muestra un MapIterator, que es similar a un Array en el sentido que se puede usar for...of para repetir los valores.

Aquí se muestra otro ejemplo de un Map, que podemos usar para demostrar estos métodos:

const map = new Map([
  [1970, 'bell bottoms'],
  [1980, 'leg warmers'],
  [1990, 'flannel'],
])

El método keys() muestra las claves:

map.keys()
Output
MapIterator {1970, 1980, 1990}

El método values() muestra los valores:

map.values()
Output
MapIterator {"bell bottoms", "leg warmers", "flannel"}

Con el método entries() se muestra una matriz de pares clave-valor:

map.entries()
Output
MapIterator {1970 => "bell bottoms", 1980 => "leg warmers", 1990 => "flannel"}

Iteración con Map

En el Map se encuentra incorporado un método forEach, similar a un Array para la iteración integrada. Sin embargo, existe cierta diferencia respecto de lo que se itera. La devolución de llamada del forEach de un Map itera value, key y el propio map, mientras que en la versión de Array se iteran item, index y el propio array.

// Map
Map.prototype.forEach((value, key, map) = () => {})

// Array
Array.prototype.forEach((item, index, array) = () => {})

Esta es una gran ventaja de los Map respecto de los Object, ya que estos deben convertirse con keys(), values() o entries() y no existe una forma sencilla de recuperar las propiedades de un Object sin convertirlo.

Para demostrar esto,iteraremos nuestro Map y registraremos los pares clave-valor en la consola:

// Log the keys and values of the Map with forEach
map.forEach((value, key) => {
  console.log(`${key}: ${value}`)
})

Esto proporcionará lo siguiente:

Output
1970: bell bottoms 1980: leg warmers 1990: flannel

Debido a que en un bucle for...of se iteran elementos iterables como los de Map y Array, podemos obtener exactamente el mismo resultado desestructurando la matriz de elementos de Map:

// Destructure the key and value out of the Map item
for (const [key, value] of map) {
  // Log the keys and values of the Map with for...of
  console.log(`${key}: ${value}`)
}

Propiedades y métodos de Map

Para una consulta rápida, en la siguiente tabla se muestra una lista de propiedades y métodos de Map:

Propiedades y métodos Descripción Resultado
set(key, value) Añade un par clave-valor a un Map. Objeto Map
delete(key) Elimina un par clave-valor de un Map por clave. Booleano
get(key) Muestra un valor por clave. Valor
has(key) Comprueba la presencia de un elemento en un Map por clave. Booleano
clear() Eliminan todos los elementos de un Map. No es pertinente
keys() Muestran todas las claves de un Map. Objeto MapIterator
values() Muestra todos los valores de un Map. Objeto MapIterator
entries() Muestra las claves y los valores de un Map como [key, value]. Objeto MapIterator
forEach() Itera el Map en el orden de inserción. No es pertinente
size Muestra el número de elementos de un Map. Número

Cuando utilizar un Map

En resumen, los Map son similares a los Object en el sentido de que almacenan pares clave-valor, pero tienen varias ventajas respecto de ellos:

  • Tamaño: los Map tienen una propiedad size, mientras que los Object no cuentan con una forma ya integrada de obtener su tamaño.
  • Iteración: los Map son directamente iterables, mientras que los Objects no.
  • Flexibilidad: los Map pueden tener cualquier tipo de datos (primitivo u Object) como clave para un valor, mientras que los Object solo pueden tener cadenas.
  • Ordenado: los Map retienen el orden de inserción, mientras que los objetos no tienen un orden garantizado.

Debido a estos factores, los Map son una poderosa estructura de datos que se debe tener en cuenta. Sin embargo, los Object también ofrecen ventajas importantes:

  • JSON: los Object funcionan sin problema con JSON.parse() y JSON.stringify(), dos funciones esenciales para trabajar con JSON, un formato de datos común que utilizan muchas API REST.
  • Funcionamiento con un solo elemento: si trabaja con un valor conocido en un Object, puede acceder a él de forma directa con la clave sin necesidad de usar un método, como el get() de Map.

Esta lista le servirá de ayuda para decidir si un Map u Object es la estructura de datos correcta para su caso de uso.

Set

Un set es un grupo de valores únicos. A diferencia de un Map, un Set es conceptualmente más parecido a un Array que a un Object, ya que es una lista de valores y no de pares clave-valor. Sin embargo, un Set no es un sustituto de los Array, sino un complemento que permite brindar respaldo adicional para trabajar con datos duplicados.

Puede inicializar Set con la sintaxis new Set().

const set = new Set()

Esto proporciona un set vacío:

Output
Set(0) {}

Se pueden añadir elementos a un set con el método add(). (Esto no debe confundirse con el método set() disponible para Map, aunque son similares).

// Add items to a Set
set.add('Beethoven')
set.add('Mozart')
set.add('Chopin')

Debido a que los sets solo pueden contener valores únicos, cualquier intento de añadir un valor ya existente se ignorará.

set.add('Chopin') // Set will still contain 3 unique values

Nota: La misma comparación de igualdad que se aplica a las claves de Map sirve para elementos de Set. Dos objetos que tengan el mismo valor y no compartan la misma referencia no se considerarán iguales.

También puede inicializar Sets con un Array de valores. Si hay valores duplicados en la matriz, se eliminarán del Set.

// Initialize a Set from an Array
const set = new Set(['Beethoven', 'Mozart', 'Chopin', 'Chopin'])
Output
Set(3) {"Beethoven", "Mozart", "Chopin"}

En cambio, un Set se puede convertir en un Array con una línea de código:

const arr = [...set]
Output
(3) ["Beethoven", "Mozart", "Chopin"]

Los sets tienen muchos de los métodos y las propiedades idénticos de los Map, incluidos delete(), has(), clear() y size.

// Delete an item
set.delete('Beethoven') // true

// Check for the existence of an item
set.has('Beethoven') // false

// Clear a Set
set.clear()

// Check the size of a Set
set.size // 0

Tenga en cuenta que los sets no tienen una una forma de acceder a un valor por medio de una clave o un índice, como Map.get(key) o arr[index].

Claves, valores y entradas para los sets

Los Map y Set cuentan con métodos keys(), values() y entries() que muestran un iterador. Sin embargo, mientras que cada uno de estos métodos tiene un propósito distinto en los Map, en los Set no se incluyen claves y, por lo tanto, estas son un alias para los valores. Esto significa que tanto keys() y values() mostrarán el mismo iterador y entries() mostrará el valor dos veces. Tiene más sentido usar únicamente values() con Set, ya que los otros dos métodos existen para la uniformidad y la compatibilidad cruzada con Map.

const set = new Set([1, 2, 3])
// Get the values of a set
set.values()
Output
SetIterator {1, 2, 3}

Iteraciones con sets

Como en el caso de los Map, los Set cuentan con un método forEach() incorporado. Debido a que los Set no tienen claves, el primer y segundo parámetro de la devolución de llamada forEach() muestran el mismo valor, por lo que no hay un caso de uso para este fuera de la compatibilidad con Map. Los parámetros de forEach() son (value, key, set).

Tanto forEach() como for...of pueden utilizarse en Set. Primero, veremos la iteración de forEach():

const set = new Set(['hi', 'hello', 'good day'])

// Iterate a Set with forEach
set.forEach((value) => console.log(value))

Luego podemos escribir la versión de for...of:

// Iterate a Set with for...of
for (const value of set) {  
    console.log(value);
}

El resultado de ambas estrategias será el siguiente:

Output
hi hello good day

Propiedades y métodos de los Set

A modo de referencia rápida, en la siguiente tabla se muestra una lista de las propiedades y los métodos de Set:

Propiedades y métodos Descripción Resultado
add(value) Añade un nuevo elemento a un Set. Objeto Set
delete(value) Elimina el elemento especificado de un Set. Booleano
has() Verifica la presencia de un elemento en un Set. Booleano
clear() Elimina todos los elementos de un Set. No es pertinente
keys() Muestra todos los valores de un Set (como values()). Objeto SetIterator
values() Muestra todos los valores de un Set (como keys()). Objeto SetIterator
entries() Muestra todos los valores de un Set como [value, value]. Objeto SetIterator
forEach() Itera el Set en el orden de inserción. No es pertinente
size Muestra el número de elementos de un Set. Número

Cuando utilizar un Set

Los Set son un elemento adicional útil para su juego de herramientas de JavaScript, en particular para trabajar con valores duplicados en los datos.

En una sola línea, podemos crear un nuevo Array sin los valores duplicados de un Array que sí los tiene.

const uniqueArray = [ ...new Set([1, 1, 2, 2, 2, 3])] // (3) [1, 2, 3]

Esto proporcionará lo siguiente:

Output
(3) [1, 2, 3]

Un Set se puede usar para encontrar la unión, intersección y diferencia entre dos conjuntos de datos. Sin embargo, los Array tienen una ventaja importante respecto de los Set para la manipulación adicional de los datos debido a los métodos sort(), map(), filter() y reduce(), y también compatibilidad directa con los métodos JSON.

Conclusión

A través de este artículo, aprendió que un Map es un grupo de pares clave-valor ordenados y que un Set es un grupo de valores únicos. Ambas estructuras de datos añaden capacidades a JavaScript y simplifican tareas comunes, como encontrar la extensión de un grupo de pares clave-valor y eliminar elementos duplicados de un conjunto de datos, respectivamente. Por otro lado, los Object y Array se han utilizado tradicionalmente para el almacenamiento y la manipulación de datos en JavaScript, y tienen compatibilidad directa con JSON, lo cual sigue haciendo que sean las estructuras de datos más esenciales, sobre todo para trabajar con API de REST. Los Map y los Set son principalmente útiles como estructuras de datos de respaldo para los Object y Array.

Si desea obtener más información sobre JavaScript, consulte la página de inicio de nuestra serie Cómo producir código en JavaScript o busque nuestra serie Cómo producir código en Node.js para hallar artículos sobre el desarrollo de backend.

0 Comments

Creative Commons License