Map y Set en JavaScript – ¿Qué son y como crearlos?

Descubre qué son Set y Map, colecciones de datos fundamentales en JavaScript que te permitirán optimizar tu código. Aprenderás para qué sirven, sus características clave y las importantes diferencias que los distinguen de los objetos y arrays tradicionales. Domina estas estructuras para gestionar datos de forma más eficiente y elegante con ejemplos super claros!!.

set y map en javascript

1. ¿Qué es un Set en JavaScript?

Un Set es un objeto integrado en JavaScript que te permite almacenar una colección de valores únicos de cualquier tipo de valor, ya sean primitivos (como números, strings, booleanos) u objetos.

Imagínalo como una bolsa donde solo puedes guardar un ejemplar de cada cosa. Si intentas meter algo que ya está dentro, simplemente se ignora.

  1. ¿Para qué sirve un Set?
  2. ¿Cuál es su principal función?
  3. ¿Cómo crear un Set en JavaScript? Paso a paso
  4. Métodos fundamentales de Set En JavaScript
  5. Iteración sobre un Set

1.1. ¿Para qué sirve un Set?

La utilidad principal de un Set radica en su capacidad para gestionar colecciones donde la unicidad de los elementos es fundamental.

Sirve para escenarios donde necesitas:

✅ Eliminar Duplicados
Es la forma más eficiente y elegante de eliminar repeticiones en un array. Simplemente al convertir un array en un Set y luego volver a array, se conservan solo los valores únicos.
✅ Verificar Rápidamente la Existencia de un Elemento
Los Set permiten consultar si un valor existe de forma extremadamente rápida con set.has(valor), mucho más eficiente que recorrer un array con includes() o indexOf().
✅ Gestionar Listas sin Duplicados y sin Orden Específico
Aunque mantienen el orden de inserción, los Set no están pensados para ordenar elementos como los arrays. Son ideales cuando solo importa tener una colección única de ítems.
✅ Realizar Operaciones de Conjuntos
Con los Set podés simular operaciones matemáticas de conjuntos como:
  • Unión: combinar elementos sin duplicados.
  • Intersección: obtener solo los elementos comunes entre dos conjuntos.
  • Diferencia: elementos que están en uno pero no en otro.
Todo esto de forma clara y muy performante.

1.2. ¿Cuál es su principal función?

La función principal de un Set es garantizar y mantener una colección de elementos únicos. En esencia, actúa como un «filtro de duplicados» automático y un «buscador de existencia» de alta velocidad para tus datos.

1.3. ¿Cómo crear un Set en JavaScript? Paso a paso

1.3.1. Crear un Set vacío

const conjunto = new Set();Lenguaje del código: JavaScript (javascript)

¿Qué está pasando aquí?

  • Usamos el constructor Set() para crear una nueva colección vacía de tipo Set.
  • El identificador conjunto es una variable JS que va a almacenar esa instancia del Set.
  • En este momento, el Set no contiene ningún valor.

¿Para qué se usa un Set vacío?

Para luego ir agregando elementos manualmente con .add():

conjunto.add("uno");
conjunto.add("dos");

console.log(conjunto); // Set(2) { 'uno', 'dos' }Lenguaje del código: JavaScript (javascript)

NOTA!!!! Si te animas y deseas ver el resultado de este ejemplo y de todos los que iremos proporcionando directamente desde tu PC, te invito a seguir estos pasos.

  1. Primero, revisa el punto 5 del siguiente artículo sobre como usar e instalar VSCode (este es el editor de código mas usado), donde encontrarás los pasos para crear la estructura necesaria para visualizar una página desde tu navegador predeterminado.
  2. Si entendiste correctamente este paso anterior, sabrás donde colocar el código script que te acabamos de proporcionar (arriba). Ahora, puedes visualizar el resultado, para ello puedes hacer lo siguiente:
    • Una vez abierta y visualizada la página, abre la consola del navegador presionando la tecla F12.
    • Ahora, dirígete a la pestaña que dice Console, y desde aquí podrás ver el resultado del script que hayamos introducido anteriormente.
  3. No te preocupes, estos pasos son más simples de lo que parecen!!!! Entonces… cada vez que dejemos un código de ejemplo como éste, puedes seguir estos pasos y ver los resultados por ti mismo. ¡Ojalá te animes!!!!

Link para descargar el editor de código Visual Studi Code!!

1.3.2. Crear un Set con valores iniciales

La forma más común y potente de crear un Set es pasándole un iterable (como un Array, un String, u otro Set) al constructor. El Set automáticamente extraerá los valores de ese iterable y los añadirá a sí mismo, ¡eliminando cualquier duplicado en el proceso!

// EJEMPLO: Inicializar un Set con un Array
const frutas = ["manzana", "banana", "naranja", "manzana", "kiwi"]; // Esto es un array
const setDeFrutas = new Set(frutas);  // Aquí lo convertimos a Set

console.log("Array original de frutas:", frutas);
// Salida: Array original de frutas: [ 'manzana', 'banana', 'naranja', 'manzana', 'kiwi' ]

console.log("Set de frutas (duplicados eliminados):", setDeFrutas);
// Salida: Set de frutas (duplicados eliminados): Set(4) { 'manzana', 'banana', 'naranja', 'kiwi' }
// Observa que 'manzana' aparece solo una vez en el Set.
Lenguaje del código: JavaScript (javascript)

Explicación del Código:

  • const frutas = ["manzana", ...];:
    • Definimos un array frutas que contiene algunos duplicados ("manzana").
  • const setDeFrutas = new Set(frutas);:
    • Aquí, pasamos el array frutas (que es un iterable) directamente al constructor Set(). El Set recorre internamente el array y añade cada elemento. Como la "manzana" se intenta añadir por segunda vez, el Set la ignora porque su principal función es la unicidad de los elementos.
  • El setDeFrutas resultante:
    • Contiene solo los elementos únicos del array original.

1.3.3. ¿Qué tipos de valores se pueden usar?

Los Set pueden almacenar cualquier tipo de dato primitivo o referencial, por ejemplo:

🔸 Con strings

const letras = new Set(["a", "b", "c", "a"]);
console.log(letras); // Set(3) { 'a', 'b', 'c' }Lenguaje del código: JavaScript (javascript)

🔸 Con booleanos

const valores = new Set([true, false, true]);
console.log(valores); // Set(2) { true, false }Lenguaje del código: JavaScript (javascript)

🔸 Con objetos

const obj1 = { nombre: "Juan" };
const obj2 = { nombre: "Juan" };

const personas = new Set([obj1, obj2]);
console.log(personas.size); // 2 (porque son objetos distintos en memoria)Lenguaje del código: JavaScript (javascript)

IMPORTANTE: Aunque obj1 y obj2 parezcan iguales, son dos objetos diferentes en memoria, por eso el Set los considera distintos.

1.3.4. Práctica usal: Crear un Set desde un array para eliminar duplicados

const lista = [10, 20, 10, 30, 40, 20]; // Este es el array
const unicos = new Set(lista); // Lo convertimos a Set

console.log(unicos); // Set(4) { 10, 20, 30, 40 } Lenguaje del código: JavaScript (javascript)

Si querés convertirlo en un array nuevamente:

const arraySinDuplicados = [...unicos];
console.log(arraySinDuplicados); // [10, 20, 30, 40]Lenguaje del código: JavaScript (javascript)

Aquí:

  • Convierte el Set de vuelta a array, usando el operador spread (...).
  • Obtenés un array nuevo, sin duplicados.
  • Este es un método rápido y profesional para limpiar arrays.

1.4. Métodos fundamentales de Set En JavaScript

Estos métodos te permiten manipular los elementos dentro de un Set. Recordá que los Set no permiten duplicados y no tienen índices, como sí ocurre con los arrays.

1.4.1. .add(valor) → Agrega un nuevo valor

Sintaxis:

const numeros = new Set();
numeros.add(1);
numeros.add(2);
numeros.add(2); // Intento duplicado
console.log(numeros); // Set(2) { 1, 2 }Lenguaje del código: JavaScript (javascript)

Explicación:

  • .add(valor) agrega el valor al Set solo si no existe aún.
  • El valor 2 solo aparece una vez, incluso si lo agregás varias veces.
  • Este método es encadenable, podés hacer:
numeros.add(3).add(4).add(5);Lenguaje del código: CSS (css)

1.4.2. .delete(valor) → Elimina un valor existente

Sintaxis:

const letras = new Set(["a", "b", "c"]);

letras.delete("b"); // Elimina el valor "b"
console.log(letras); // Set(2) { 'a', 'c' }

const resultado = letras.delete("z"); // Intenta borrar algo que no existe
console.log(resultado); // falseLenguaje del código: JavaScript (javascript)

Explicación:

  • .delete(valor) elimina un valor si existe y devuelve true.
  • Si el valor no está en el Set, no hace nada y devuelve false.
  • El Set no lanza errores si tratás de borrar un valor inexistente.

1.4.3. .has(valor) → Verifica si existe un valor

Sintaxis:

const frutas = new Set(["manzana", "banana"]);

console.log(frutas.has("banana")); // true
console.log(frutas.has("pera"));   // falseLenguaje del código: JavaScript (javascript)

Explicación:

  • .has(valor) devuelve true si el valor existe en el Set, y false si no.
  • Es útil para validar si ya está incluido un valor antes de agregarlo o hacer lógica condicional.

1.4.4. .clear() → Elimina todos los valores del Set

Sintaxis:

const numeros = new Set([1, 2, 3]);

console.log(numeros.size); // 3 antes del clear
numeros.clear(); // Borra todos los valores
console.log(numeros); // Set(0) {}Lenguaje del código: JavaScript (javascript)

Explicación:

  • .clear() borra todos los elementos del Set.
  • El Set queda vacío, pero sigue existiendo (no se elimina la variable).
  • Ideal para reiniciar datos temporales, listas dinámicas, etc.

1.4.5. .size → Devuelve la cantidad de elementos

Sintaxis:

const conjunto = new Set(["x", "y", "z"]);

console.log(conjunto.size); // 3Lenguaje del código: JavaScript (javascript)

Explicación:

  • .size es una propiedad (no un método), por eso no lleva paréntesis.
  • Equivalente a .length en los arrays.
  • Siempre devuelve el número de elementos únicos dentro del Set.

1.4.6. Resumen en tabla

Método / Propiedad¿Qué hace?Tipo
.add(valor)Agrega un valor si no existeMétodo
.delete(valor)Elimina un valor existenteMétodo
.has(valor)Verifica si el valor está presenteMétodo
.clear()Borra todos los valores del SetMétodo
.sizeDevuelve la cantidad de elementos únicosPropiedad

1.5. Iteración sobre un Set

¿Qué significa iterar sobre un Set?

Significa recorrer cada uno de los elementos únicos que contiene la colección, uno por uno, para realizar alguna operación con ellos. A diferencia de los arrays, donde el orden está implícitamente indexado, en un Set el concepto principal es la presencia o ausencia de un elemento.

Sin embargo, los Sets en JavaScript mantienen el orden de inserción, lo que significa que los elementos se iterarán en el mismo orden en que fueron añadidos (o en el orden en que aparecieron en el iterable inicial si se usó el constructor con un array, por ejemplo).

Las dos formas más comunes y recomendadas para iterar sobre un Set son:

  • El bucle for...of
  • El método forEach().

1.5.1. for...of Loop

El bucle for...of es la forma más moderna y sencilla de iterar sobre cualquier iterable en JavaScript, y los Sets son iterables. Proporciona una manera directa de obtener cada valor del Set en cada iteración.

// Creamos un Set de nombres de estudiantes
const estudiantesActivos = new Set(["Ana", "Luis", "Sofía", "Pedro"]);

console.log("--- Iterando con for...of ---");
for (const estudiante of estudiantesActivos) {
  console.log(`Hola, ${estudiante}!`);
}
/* Salida:
--- Iterando con for...of ---
Hola, Ana!
Hola, Luis!
Hola, Sofía!
Hola, Pedro!
*/
Lenguaje del código: JavaScript (javascript)

Explicación del Código:

  1. const estudiantesActivos = new Set(["Ana", "Luis", "Sofía", "Pedro"]);: Inicializamos un Set con cuatro nombres.
  2. for (const estudiante of estudiantesActivos) { ... }: Esta es la sintaxis del bucle for...of.
    • const estudiante: En cada iteración, la variable estudiante tomará el valor del siguiente elemento del Set.
    • of estudiantesActivos: Indica que estamos iterando sobre el Set llamado estudiantesActivos.
  3. console.log(Hola, ${estudiante}!);: Dentro del bucle, accedemos al valor actual (estudiante) y lo imprimimos en la consola.

El bucle for...of es ideal cuando solo necesitas acceder a los valores del Set y realizar una acción por cada uno.

1.5.2. forEach() método

El método forEach() es un método de instancia disponible en los Sets que te permite ejecutar una función de callback una vez por cada elemento en el Set. Es muy similar al forEach() de los arrays, pero con una pequeña peculiaridad en sus argumentos.

La función de callback que le pasas a forEach() recibe hasta tres argumentos:

  1. value (valor actual):
    • El valor del elemento actual que se está procesando en el Set.
  2. key (clave):
    • En el caso de un Set, la «clave» es idéntica al value. Esto se debe a que los Sets no tienen claves separadas como los Maps o los objetos; el valor es la clave. Este argumento está presente para mantener una firma consistente con el forEach() de Map.
  3. set (el Set completo):
    • Una referencia al Set sobre el cual se está iterando. Esto es útil si necesitas interactuar con el Set completo dentro del callback.
// Volvemos a usar nuestro Set de nombres de estudiantes
const estudiantesActivos = new Set(["Ana", "Luis", "Sofía", "Pedro"]);

console.log("\n--- Iterando con forEach() ---");
estudiantesActivos.forEach((valor, clave, setCompleto) => {
  console.log(`Valor: ${valor}, Clave (igual al valor): ${clave}`);
  // También podríamos usar setCompleto aquí si lo necesitáramos
  // console.log("Set completo dentro del forEach:", setCompleto);
});
/* Salida:
--- Iterando con forEach() ---
Valor: Ana, Clave (igual al valor): Ana
Valor: Luis, Clave (igual al valor): Luis
Valor: Sofía, Clave (igual al valor): Sofía
Valor: Pedro, Clave (igual al valor): Pedro
*/

// Otro ejemplo de forEach, usando solo el valor
console.log("\n--- forEach() usando solo el valor ---");
estudiantesActivos.forEach(estudiante => {
  if (estudiante.startsWith("A")) {
    console.log(`${estudiante} comienza con 'A'.`);
  }
});
/* Salida:
--- forEach() usando solo el valor ---
Ana comienza con 'A'.
*/
Lenguaje del código: JavaScript (javascript)

Explicación del Código:

  1. estudiantesActivos.forEach((valor, clave, setCompleto) => { ... });: Llamamos al método forEach() en nuestro Set. Le pasamos una función de flecha como callback.
  2. Dentro de la función de flecha:
    • valor: En cada iteración, valor contendrá el elemento actual del Set (ej., «Ana», luego «Luis», etc.).
    • clave: También contendrá el mismo elemento actual («Ana», «Luis», etc.). Este argumento es un vestigio de la similitud con Map.prototype.forEach().
    • setCompleto: Contendrá una referencia al Set estudiantesActivos en sí.
  3. console.log(...): Imprimimos los valores para demostrar lo que cada argumento contiene.

El método forEach() es útil cuando necesitas una mayor flexibilidad, como acceder al Set completo dentro del callback, o si estás acostumbrado a su uso en arrays. Sin embargo, para la mayoría de los casos donde solo necesitas el valor, el for...of es a menudo más conciso y legible.

2. ¿Qué es un Map en JavaScript?

Un Map es una colección ordenada de pares clave-valor donde tanto las claves como los valores pueden ser de cualquier tipo de dato. Esto incluye tipos primitivos (strings, números, booleanos, null, undefined) y, crucialmente, objetos (incluyendo funciones y otros Maps o Sets).

A primera vista, esto puede sonar similar a un objeto literal ({}), pero Map ofrece ventajas cruciales y un comportamiento más robusto para ciertos escenarios.

  1. ¿Para qué sirve un Map?
  2. ¿Cuál es su función principal?
  3. Diferencias clave de Map con los objetos
  4. ¿Cómo crear un Map?
  5. Métodos fundamentales de Map
  6. Iteración sobre un Map

2.1. ¿Para qué sirve un Map?

Map es la herramienta ideal cuando necesitas asociar información de forma flexible y garantizar un orden específico.

Sirve para escenarios donde:

✅ Necesitás usar cualquier tipo de dato como clave
A diferencia de los objetos literales, cuyas claves se convierten automáticamente a strings (salvo los Symbol), un Map te permite usar objetos, funciones o incluso NaN como claves distintas. Esto es invaluable cuando tus datos no se pueden representar convenientemente con claves de tipo string.
✅ El orden de inserción de los elementos es importante
Los Map recuerdan el orden exacto en que se insertaron los pares clave-valor. Al iterar sobre un Map, los elementos aparecen en ese mismo orden. Esto no está garantizado de la misma forma con los objetos literales, especialmente en versiones antiguas de JavaScript.
✅ Realizás frecuentes adiciones y eliminaciones de pares clave-valor
Los Map están optimizados para agregar y eliminar elementos, lo que les da un rendimiento predecible y eficiente en operaciones dinámicas.
✅ Necesitás saber fácilmente el número de elementos
Los Map incluyen una propiedad size que te da directamente la cantidad de pares clave-valor contenidos. No es necesario calcularla manualmente como ocurre con los objetos usando Object.keys().length.

2.2. ¿Cuál es su función principal?

La función principal de un Map es proporcionar un mecanismo robusto y flexible para almacenar y gestionar colecciones de pares clave-valor, donde la flexibilidad en los tipos de claves y el mantenimiento del orden de inserción son cruciales.

Actúa como un diccionario o tabla de búsqueda altamente eficiente que no está limitado por las restricciones de los objetos literales en cuanto a los tipos de claves y el orden de los elementos.

2.3. Diferencias clave de Map con los objetos

CaracterísticaObjectMap
ClavesSolo strings o symbolsCualquier tipo de dato
Orden de elementosNo garantizadoConserva el orden de inserción
IteraciónNo directamente iterableTotalmente iterable
Tamaño (.length o .size)No tiene .lengthTiene .size
Optimizado paraAcceso y estructuras genéricasRendimiento en operaciones frecuentes de clave/valor

2.4. ¿Cómo crear un Map?

Hay dos formas principales de hacerlo, similar a cómo se trabaja con los Sets.

2.4.1. Crear un Map vacío: new Map()

La forma más básica es invocar al constructor Map() sin ningún argumento. Esto te da un Map inicialmente vacío al que puedes añadir pares clave-valor más tarde usando el método set().

// Paso 1: Declarar un Map vacío
const miPrimerMap = new Map();

console.log("miPrimerMap (vacío):", miPrimerMap);
// Salida: miPrimerMap (vacío): Map(0) {}
// La salida muestra un objeto Map con 0 elementos.
Lenguaje del código: JavaScript (javascript)

Explicación del código:

  1. const miPrimerMap = new Map();:
    • Aquí estamos utilizando el operador new para llamar al constructor Map(). Cuando se invoca sin argumentos, se crea una nueva instancia de Map que no contiene ningún par clave-valor.
  2. console.log("miPrimerMap (vacío):", miPrimerMap);:
    • Al imprimir la variable miPrimerMap, la consola muestra que es un objeto Map y que su tamaño actual es 0.

2.4.2. Inicializar un Map con un array de pares [key, value]

Esta es la forma más común y efectiva de crear un Map si ya tienes los datos que deseas almacenar. Puedes pasarle al constructor Map() un iterable (generalmente un array) donde cada elemento del iterable es a su vez un array de dos elementos: el primero es la clave y el segundo es el valor.

El Map procesa estos «pares» y los añade directamente a la colección. Si hay claves duplicadas en el array inicial, el último valor asociado a esa clave será el que prevalezca.

// Ejemplo 1: Inicializar un Map con un array de arrays [clave, valor]
const datosDeUsuarios = [
  ["id_001", { nombre: "Ana", edad: 28 }],
  ["id_002", { nombre: "Luis", edad: 34 }],
  ["id_003", { nombre: "Sofía", edad: 22 }]
];

const mapaUsuarios = new Map(datosDeUsuarios);

console.log("Array original de datos de usuarios:", datosDeUsuarios);
/* Salida:
Array original de datos de usuarios: [
  [ 'id_001', { nombre: 'Ana', edad: 28 } ],
  [ 'id_002', { nombre: 'Luis', edad: 34 } ],
  [ 'id_003', { nombre: 'Sofía', edad: 22 } ]
]
*/

console.log("Map de usuarios creado:", mapaUsuarios);
/* Salida:
Map de usuarios creado: Map(3) {
  'id_001' => { nombre: 'Ana', edad: 28 },
  'id_002' => { nombre: 'Luis', edad: 34 },
  'id_003' => { nombre: 'Sofía', edad: 22 }
}
*/
Lenguaje del código: JavaScript (javascript)

Explicación del código:

  • const datosDeUsuarios = [ ... ];:
    • Definimos un array llamado datosDeUsuarios. Cada elemento de este array es, a su vez, un pequeño array de dos elementos ([clave, valor]). Aquí, las claves son strings (IDs) y los valores son objetos con información del usuario.
  • const mapaUsuarios = new Map(datosDeUsuarios);:
    • Pasamos datosDeUsuarios directamente al constructor Map(). El constructor itera sobre datosDeUsuarios y para cada mini-array [clave, valor], añade ese par al mapaUsuarios.
  • El mapaUsuarios resultante:
    • Contiene los tres pares clave-valor, manteniendo el orden en que fueron definidos en el array original.

2.5. Métodos fundamentales de Map

2.5.1. .set(clave, valor)

Agrega un nuevo par clave/valor o actualizar el valor de una clave existente.

const mapa = new Map();

mapa.set("nombre", "Juan");
mapa.set("edad", 30);

console.log(mapa); 
// Map(2) { 'nombre' => 'Juan', 'edad' => 30 }Lenguaje del código: JavaScript (javascript)

Explicación:

  • Si la clave no existe, la agrega.
  • Si ya existe, simplemente actualiza el valor.
  • Este método retorna el Map actualizado, lo que permite el encadenamiento (chaining).

2.5.2. .get(clave)

Obtener el valor asociado a una clave específica.

const persona = new Map();
persona.set("nombre", "Lucía");

console.log(persona.get("nombre")); 
// "Lucía"

console.log(persona.get("edad"));   
// undefined (porque no existe esa clave)Lenguaje del código: JavaScript (javascript)

Explicación:

  • Retorna el valor correspondiente a la clave.
  • Si la clave no existe, retorna undefined.

2.5.3. .has(clave)

Verificar si una clave existe en el Map.

const inventario = new Map([
  ["manzanas", 10],
  ["naranjas", 5]
]);

console.log(inventario.has("manzanas")); // true
console.log(inventario.has("bananas"));  // falseLenguaje del código: JavaScript (javascript)

Explicación:

  • Retorna true si la clave está presente en el Map.
  • Muy útil para validaciones previas.

2.5.4. .delete(clave)

Eliminar un par clave/valor del Map.

const mapa = new Map([
  ["color", "rojo"],
  ["forma", "círculo"]
]);

mapa.delete("color");

console.log(mapa); 
// Map(1) { 'forma' => 'círculo' }Lenguaje del código: JavaScript (javascript)

Explicación:

  • Elimina el par correspondiente a la clave.
  • Devuelve true si lo eliminó correctamente, false si la clave no existía.

2.5.5. .clear()

Eliminar todos los pares clave/valor del Map.

const datos = new Map([
  ["clave1", "valor1"],
  ["clave2", "valor2"]
]);

datos.clear();

console.log(datos); 
// Map(0) {}Lenguaje del código: JavaScript (javascript)

Explicación:

  • Borra todo el contenido del Map.
  • Es útil para reiniciar estructuras de datos.

2.5.6. .size

Saber cuántos pares clave/valor hay en el Map.

const mapa = new Map();
mapa.set("x", 100);
mapa.set("y", 200);

console.log(mapa.size); 
// 2Lenguaje del código: JavaScript (javascript)

Explicación:

  • Propiedad (no método) que indica cuántos pares contiene el Map.
  • Se accede sin paréntesis.

2.6. Iteración sobre un Map

Iterar sobre un Map significa recorrer cada uno de los pares clave-valor que contiene, uno por uno, para realizar alguna operación o acceder a su información. A diferencia de los Sets que solo tienen valores únicos, los Maps manejan tanto una clave como un valor asociado.

RECIUERDA! Los Maps en JavaScript, al igual que los Sets, mantienen el orden de inserción, lo que significa que los elementos se iterarán en el mismo orden en que fueron añadidos.

Hay varias formas de iterar sobre un Map, cada una útil para diferentes escenarios:

  • for...of con entries(), keys(), values()
  • forEach() método

2.6.1. for...of con entries(), keys(), values()

El bucle for...of es la forma más moderna y versátil de iterar sobre cualquier iterable en JavaScript. Los Maps ofrecen métodos específicos que devuelven iteradores para sus entradas, claves o valores.

a) map.entries(): ITERAR SOBRE PARES [clave, valor]

Este es el comportamiento predeterminado cuando se itera directamente sobre un Map con for...of. El método entries() devuelve un objeto iterable que contiene un array [clave, valor] para cada par en el Map.

Usando la desestructuración de arrays en el bucle for...of, puedes acceder directamente a la clave y al valor.

// Creamos un Map de puntuaciones de jugadores
const puntuacionesJugadores = new Map([
  ["Ana", 150],
  ["Luis", 200],
  ["Sofía", 180],
]);

console.log("--- Iterando sobre pares [clave, valor] con for...of y entries() ---");
// Puedes omitir .entries() ya que es el comportamiento por defecto de for...of en Map
for (const [nombre, puntuacion] of puntuacionesJugadores.entries()) {
  console.log(`${nombre} tiene ${puntuacion} puntos.`);
}
/* Salida:
--- Iterando sobre pares [clave, valor] con for...of y entries() ---
Ana tiene 150 puntos.
Luis tiene 200 puntos.
Sofía tiene 180 puntos.
*/
Lenguaje del código: JavaScript (javascript)

Explicación del código:

  1. const puntuacionesJugadores = new Map([...]);: Inicializamos un Map con algunos nombres de jugadores y sus puntuaciones.
  2. for (const [nombre, puntuacion] of puntuacionesJugadores.entries()) { ... }:
    • puntuacionesJugadores.entries(): Este método devuelve un objeto iterable. Cada vez que el bucle pide el siguiente elemento, este iterable proporciona un array de dos elementos: [clave, valor].
    • const [nombre, puntuacion]: Aquí usamos la desestructuración de arrays. En cada iteración, el primer elemento del array ([clave, valor]) se asigna a la variable nombre, y el segundo elemento se asigna a puntuacion. Esto hace que el código sea muy legible.
  3. console.log(...): Dentro del bucle, utilizamos las variables nombre y puntuacion para mostrar la información de cada jugador.
b) map.keys(): ITERAR SOBRE LAS CLAVES

El método keys() devuelve un objeto iterable que contiene SOLO las claves de cada par clave-valor en el Map.

const productosEnStock = new Map([
  ["Laptop", 10],
  ["Teclado", 50],
  ["Mouse", 100],
]);

console.log("\n--- Iterando sobre las claves con for...of y keys() ---");
for (const nombreProducto of productosEnStock.keys()) {
  console.log(`Producto disponible: ${nombreProducto}`);
}
/* Salida:
--- Iterando sobre las claves con for...of y keys() ---
Producto disponible: Laptop
Producto disponible: Teclado
Producto disponible: Mouse
*/
Lenguaje del código: JavaScript (javascript)

Explicación del código:

  1. productosEnStock.keys(): Este método retorna un iterable que, en cada paso, proporciona solo la clave del siguiente par del Map.
  2. for (const nombreProducto of productosEnStock.keys()) { ... }: La variable nombreProducto recibe la clave de cada iteración.
  3. Este enfoque es útil cuando solo te interesa la lista de las claves en tu Map y no sus valores asociados.
c) map.values(): ITERAR SOBRE LOS VALORES

El método values() devuelve un objeto iterable que contiene SOLO los valores de cada par clave-valor en el Map.

const temperaturasCiudades = new Map([
  ["Buenos Aires", 25],
  ["Madrid", 30],
  ["Londres", 18],
]);

console.log("\n--- Iterando sobre los valores con for...of y values() ---");
for (const temperatura of temperaturasCiudades.values()) {
  console.log(`Una temperatura registrada: ${temperatura}°C`);
}
/* Salida:
--- Iterando sobre los valores con for...of y values() ---
Una temperatura registrada: 25°C
Una temperatura registrada: 30°C
Una temperatura registrada: 18°C
*/
Lenguaje del código: JavaScript (javascript)

Explicación del código:

  1. temperaturasCiudades.values(): Este método retorna un iterable que, en cada paso, proporciona solo el valor del siguiente par del Map.
  2. for (const temperatura of temperaturasCiudades.values()) { ... }: La variable temperatura recibe el valor de cada iteración.
  3. Este método es ideal cuando solo necesitas procesar los valores almacenados en el Map y las claves no son relevantes para la tarea actual.

2.6.2. forEach() método

El método forEach() es un método de instancia que te permite ejecutar una función de callback una vez por cada par clave-valor en el Map. Es similar al forEach() de los arrays y Sets, pero sus argumentos para el callback están específicamente diseñados para Maps.

La función de callback que le pasas a forEach() recibe hasta tres argumentos en un orden específico:

  1. value (valor actual): El valor del elemento actual que se está procesando.
  2. key (clave actual): La clave del elemento actual que se está procesando.
  3. map (el Map completo): Una referencia al Map sobre el cual se está iterando. Esto es útil si necesitas interactuar con el Map completo (por ejemplo, acceder a otros elementos) dentro del callback
const usuariosConRoles = new Map([
  ["[email protected]", "Administrador"],
  ["[email protected]", "Editor"],
  ["[email protected]", "Espectador"],
]);

console.log("\n--- Iterando con forEach() ---");
usuariosConRoles.forEach((rol, email, mapCompleto) => {
  console.log(`Email: ${email}, Rol: ${rol}`);
  // Opcional: usar mapCompleto si lo necesitas dentro del callback
  // console.log("Tamaño del Map dentro del forEach:", mapCompleto.size);
});
/* Salida:
--- Iterando con forEach() ---
Email: [email protected], Rol: Administrador
Email: [email protected], Rol: Editor
Email: [email protected], Rol: Espectador
*/

// Otro ejemplo de forEach, usando solo los argumentos que necesitas
console.log("\n--- forEach() usando solo valor y clave ---");
usuariosConRoles.forEach((rol, email) => {
  if (rol === "Editor") {
    console.log(`${email} tiene el rol de ${rol}.`);
  }
});
/* Salida:
--- forEach() usando solo valor y clave ---
[email protected] tiene el rol de Editor.
*/
Lenguaje del código: JavaScript (javascript)

Explicación del código:

  1. usuariosConRoles.forEach((rol, email, mapCompleto) => { ... });: Llamamos al método forEach() en nuestro Map. Le pasamos una función de flecha como callback.
  2. Dentro de la función de flecha:
    • rol: Contendrá el valor actual del par (ej., «Administrador»).
    • email: Contendrá la clave actual del par (ej., «[email protected]»).
    • mapCompleto: Contendrá una referencia al Map usuariosConRoles en sí.
  3. console.log(...): Imprimimos los valores para demostrar lo que cada argumento contiene.

3. WeakSet y WeakMap: La gestión de la memoria en JavaScript

Para entender WeakSet y WeakMap, primero necesitamos comprender un concepto fundamental en JavaScript: el Garbage Collection (recolección de basura).

¿Qué es la Garbage Collection? Concepto de referencias débiles:

En JavaScript, la gestión de la memoria es automática. El motor de JavaScript tiene un proceso llamado Garbage Collector (GC) que se encarga de liberar la memoria ocupada por objetos que ya no son «alcanzables» o «referenciados» por ninguna parte de tu código. Esto evita que tu aplicación consuma demasiada memoria y sufra de «fugas de memoria» (memory leaks).

Cuando tienes una referencia «fuerte» a un objeto, significa que mientras esa referencia exista en tu código, el Garbage Collector no puede liberar la memoria ocupada por ese objeto, incluso si no lo estás usando activamente en ese momento. Los Arrays, Sets y Maps normales crean referencias fuertes a sus elementos o claves/valores.

Las referencias débiles son diferentes. Una referencia débil a un objeto no impide que el Garbage Collector libere la memoria de ese objeto si no hay otras referencias fuertes apuntando a él. Si el objeto es recolectado, la entrada en la colección débil (WeakSet/WeakMap) simplemente desaparece automáticamente.

¿Por qué es importante el Garbage Collection?

Es vital para la eficiencia y estabilidad de tu aplicación. Si tu código crea objetos y mantiene referencias a ellos indefinidamente sin necesitarlos, la memoria se acumulará, haciendo que tu aplicación se vuelva más lenta o incluso se bloquee. Las referencias débiles son una herramienta avanzada para manejar ciertos escenarios de memoria donde no quieres que la presencia de un objeto en una colección impida su recolección.

3.1. WeakSet: Colecciones débiles de objetos únicos

Un WeakSet es una colección de objetos únicos. Su característica principal es que las referencias a los objetos que contiene son débiles.

  1. Solo puede contener objetos:
    • No puedes almacenar valores primitivos (strings, números, booleanos, null, undefined) en un WeakSet. Solo acepta objetos.
  2. No es iterable:
    • No puedes usar for...of o forEach() para recorrer los elementos de un WeakSet. Tampoco tiene una propiedad size. Esto se debe a que, al ser sus referencias débiles, los elementos podrían desaparecer en cualquier momento si el GC actúa, lo que haría que la iteración fuera impredecible.
  3. Propósito:
    • Mantener un registro de objetos sin crear referencias fuertes. Si un objeto solo está referenciado por un WeakSet (y ninguna otra parte del código tiene una referencia fuerte a él), puede ser recolectado por el GC. Cuando el objeto es recolectado, se elimina automáticamente del WeakSet.

MÉTODOS CLAVE DE WeakSet:

  • add(value): Añade un objeto al WeakSet.
  • delete(value): Elimina un objeto del WeakSet.
  • has(value): Verifica si un objeto está presente en el WeakSet.

CASOS DE USO DE WeakSet:

  • Marcar objetos ya procesados o visitados: Imagina un algoritmo que debe procesar una lista de objetos y no quieres procesar el mismo objeto dos veces. Un WeakSet puede servir como un registro de «objetos ya vistos». Si un objeto se sale de alcance y es recolectado, no hay problema, porque ya no lo necesitamos.
    • Ejemplo:
let obj1 = { id: 1 };
let obj2 = { id: 2 };
let obj3 = { id: 3 };

const objetosProcesados = new WeakSet();

objetosProcesados.add(obj1);
objetosProcesados.add(obj2);

console.log("¿Obj1 procesado?", objetosProcesados.has(obj1)); // Salida: ¿Obj1 procesado? true
console.log("¿Obj3 procesado?", objetosProcesados.has(obj3)); // Salida: ¿Obj3 procesado? false

// Si obj1 ya no es referenciado en ningún otro lugar, el GC puede eliminarlo.
// Cuando lo haga, obj1 se eliminará automáticamente de 'objetosProcesados'.
obj1 = null; // Quita la referencia fuerte a obj1
// En un entorno de navegador/Node.js, el GC eventualmente actuará.
// No podemos verificar inmediatamente si ha sido recolectado,
// ya que el GC es no determinista.
// Sin embargo, sabemos que la referencia del WeakSet no lo retiene.Lenguaje del código: JavaScript (javascript)

Explicación: objetosProcesados guarda referencias débiles a obj1 y obj2. Cuando obj1 = null se ejecuta, si obj1 no tiene otras referencias fuertes, se vuelve elegible para ser recolectado. El WeakSet no impide esto.

  • Gestionar un registro de instancias de una clase: Útil para saber qué instancias de una clase particular están «vivas» sin impedir que se destruyan cuando ya no se necesitan.
    • Ejemplo:
class ComponenteUI {
  constructor(name) {
    this.name = name;
    ComponenteUI.activeInstances.add(this); // Añadir esta instancia al WeakSet
    console.log(`Componente ${this.name} creado.`);
  }

  destroy() {
    ComponenteUI.activeInstances.delete(this); // Eliminar del WeakSet
    console.log(`Componente ${this.name} destruido.`);
    // Aquí podrías nullificar otras referencias fuertes si las hubiera
  }

  static activeInstances = new WeakSet(); // Un WeakSet para todas las instancias activas
}

let btn1 = new ComponenteUI("Botón Guardar");
let btn2 = new ComponenteUI("Botón Cancelar");

console.log("¿Botón Guardar está en instancias activas?", ComponenteUI.activeInstances.has(btn1)); // true
console.log("¿Botón Cerrar está en instancias activas?", ComponenteUI.activeInstances.has(new ComponenteUI("Botón Cerrar"))); // false (porque es una nueva instancia temporal)

// Cuando btn1 se establece a null, puede ser recolectado, y el WeakSet lo liberará automáticamente.
btn1 = null;
// (El GC se encargará, no podemos hacer un console.log inmediato para verificar la recolección)Lenguaje del código: JavaScript (javascript)

Explicación: El WeakSet ComponenteUI.activeInstances mantiene un registro de las instancias ComponenteUI. Si btn1 ya no es referenciado en ningún otro lugar de tu código (por ejemplo, si su elemento DOM asociado fuera eliminado y no hubiera otras variables apuntando a él), el GC puede limpiarlo, y automáticamente desaparecerá del WeakSet, sin que tengas que gestionarlo manualmente.

    3.2. WeakMap: Colecciones débiles de Clave-Valor con claves de objeto

    Un WeakMap es una colección de pares clave-valor similar a un Map, pero con dos diferencias cruciales relacionadas con las referencias débiles.

    1. Las claves solo pueden ser objetos:
      • Al igual que WeakSet, WeakMap no permite claves primitivas (strings, números, booleanos, null, undefined). Las claves deben ser objetos. Si una clave de objeto es recolectada por el GC, su entrada completa (clave y valor) se elimina automáticamente del WeakMap.
    2. No es iterable:
      • No puedes iterar sobre las claves o valores de un WeakMap con for...of ni forEach(), y no tiene una propiedad size. Esto es por la misma razón que en WeakSet: los elementos pueden desaparecer en cualquier momento debido al GC.
    3. Propósito:
      • Asociar datos a objetos sin crear referencias fuertes a esos objetos. Es ideal para «extender» objetos con datos adicionales que no deben impedir que el objeto original sea recolectado.

    MÉTODOS CLAVE DE WeakMap:

    • set(key, value): Asocia un value a una key (objeto).
    • get(key): Recupera el value asociado a una key.
    • delete(key): Elimina el par clave-valor.
    • has(key): Verifica si una key (objeto) está presente.

    CASOS DE USO DE WeakMap:

    • Almacenar metadatos privados de objetos: Los WeakMaps son perfectos para almacenar información «extra» sobre objetos que no debería ser parte del objeto en sí, y que debe ser limpiada automáticamente cuando el objeto original deja de existir. Esto es útil para implementar datos privados o «ocultos» asociados a instancias.
    const _privateData = new WeakMap(); // Un WeakMap para datos privados
    
    class Usuario {
      constructor(id, nombre) {
        this.id = id;
        this.nombre = nombre;
        // Almacenar un contador de acceso privado para este usuario
        _privateData.set(this, { accessCount: 0 });
      }
    
      incrementAccessCount() {
        const data = _privateData.get(this);
        if (data) {
          data.accessCount++;
          console.log(`Contador de acceso para ${this.nombre}: ${data.accessCount}`);
        }
      }
    
      getAccessCount() {
        return _privateData.get(this)?.accessCount || 0;
      }
    }
    
    let user1 = new Usuario(1, "Alice");
    user1.incrementAccessCount(); // Contador de Alice: 1
    user1.incrementAccessCount(); // Contador de Alice: 2
    
    let user2 = new Usuario(2, "Bob");
    user2.incrementAccessCount(); // Contador de Bob: 1
    
    console.log("Contador de Alice:", user1.getAccessCount()); // Salida: Contador de Alice: 2
    
    // Si user1 se vuelve null (pierde su referencia fuerte),
    // tanto el objeto Usuario como su entrada en _privateData
    // pueden ser recolectados por el GC.
    user1 = null;
    // No hay forma de verificar user1 en _privateData después de la recolecciónLenguaje del código: JavaScript (javascript)

    Explicación: El WeakMap _privateData asocia un objeto de datos privado ({ accessCount: 0 }) a cada instancia de Usuario. La clave en el WeakMap es la propia instancia de Usuario. Si una instancia de Usuario (ej., user1) ya no es accesible por referencias fuertes en el código, el GC la liberará, y su entrada correspondiente en _privateData también se eliminará automáticamente, evitando fugas de memoria.

    • Caching de resultados basados en objetos: Cuando necesitas almacenar en caché los resultados de cálculos costosos asociados a objetos específicos. Si el objeto original es recolectado, la entrada del caché asociada a él también se limpia automáticamente.
    const cacheDeCalculos = new WeakMap();
    
    function calcularComplejo(objetoDeDatos) {
      if (cacheDeCalculos.has(objetoDeDatos)) {
        console.log("¡Desde caché!");
        return cacheDeCalculos.get(objetoDeDatos);
      }
    
      console.log("Calculando...");
      // Simular un cálculo pesado
      const resultado = objetoDeDatos.valor * 10 + 5;
      cacheDeCalculos.set(objetoDeDatos, resultado);
      return resultado;
    }
    
    let data1 = { valor: 5 };
    let data2 = { valor: 10 };
    
    console.log(calcularComplejo(data1)); // Calculando... 55
    console.log(calcularComplejo(data1)); // ¡Desde caché! 55
    
    console.log(calcularComplejo(data2)); // Calculando... 105
    
    // Si data1 ya no es referenciado en ninguna parte,
    // su entrada en cacheDeCalculos se eliminará automáticamente.
    data1 = null;
    // Si más tarde se crea un nuevo objeto { valor: 5 } (no la misma instancia),
    // se volverá a calcular.Lenguaje del código: JavaScript (javascript)

    Explicación: cacheDeCalculos almacena resultados de calcularComplejo usando el objeto de datos como clave. Cuando data1 se establece a null, si el GC lo recolecta, la entrada { data1 => 55 } se elimina del WeakMap, liberando esa memoria del caché de forma automática.

      4. ¿Cuándo usar y cuándo no usar Set/Map?

      Como profesional de la programación, no se trata solo de conocer las herramientas, sino de saber cuándo aplicarlas correctamente. Usar Set o Map ciegamente, cuando otras estructuras son más adecuadas, puede llevar a código más complejo, menos eficiente o difícil de entender. Aquí te explico las consideraciones clave.

      4.1. ¿Cuándo NO Usar Set/Map?

      Aunque Set y Map son potentes, no son la solución para todos los problemas de gestión de datos. Debes evitar usarlos en los siguientes escenarios:

      1. Si solo necesitas un objeto simple con claves string y el orden de inserción no es estrictamente crítico (o solo para claves no-numéricas): Los objetos literales ({}) son excelentes para colecciones simples de pares clave-valor donde las claves son cadenas de texto o símbolos. Son familiares para la mayoría de los desarrolladores JavaScript y a menudo más concisos de escribir para estos casos básicos.
      2. Si solo necesitas un array para mantener un orden específico de elementos y no necesitas unicidad o mapeo complejo: Los arrays ([]) son la estructura de datos por excelencia para listas ordenadas de elementos. Si el orden de los elementos es tu preocupación principal y los duplicados son aceptables (o se manejarán de otra forma simple), un array es la elección correcta.

      4.2. ¿La elección de Set/Map mejora la legibilidad y mantenibilidad del código para otros desarrolladores?

      Esta es una pregunta crucial que todo profesional debe hacerse. La respuesta es un rotundo: ¡Sí, cuando se usan en el contexto adecuado!

      • Legibilidad mejorada:
        • Cuando un desarrollador ve un Set, su cerebro inmediatamente piensa: «Ah, esta es una colección de elementos únicos». La intención del código es clara sin necesidad de comentarios adicionales. Si, en cambio, usas un array y luego una lógica compleja para eliminar duplicados o verificar la unicidad, el código es más denso y menos claro a primera vista.
        • Cuando ven un Map, entienden que hay una relación clave-valor donde las claves pueden ser algo más que simples strings, y que el orden de inserción se mantiene. Esto comunica que el problema que se está resolviendo requiere esa flexibilidad o garantía de orden.
      • Mantenibilidad mejorada:
        • El código que utiliza Set y Map de forma apropiada es generalmente más conciso y con menos errores potenciales. Menos código significa menos lugares para bugs.
        • Si las necesidades futuras del proyecto cambian (por ejemplo, de repente necesitas que las claves sean objetos en lugar de strings), un Map se adaptará fácilmente, mientras que un objeto literal requeriría una refactorización significativa.
        • Los métodos de Set y Map (add, delete, has, get, set) son intuitivos y estandarizados. Un desarrollador que conoce JavaScript entenderá rápidamente lo que hace tu código.

      CONCLUSIÓN: La elección de Set y Map no es arbitraria. Responden a necesidades específicas de manejo de colecciones que los objetos literales y los arrays no cubren de forma tan elegante o eficiente. Al usarlos apropiadamente, no solo estás escribiendo código más performante, sino también más claro y fácil de mantener, lo cual es una marca de un desarrollador profesional.

      5. Preguntas frecuentes y resumen sobre Set y Map en JS.

      Set y Map son estructuras de datos. Set almacena valores únicos sin duplicados, mientras que Map guarda pares clave-valor con claves de cualquier tipo. Se usan para manejar datos de forma más eficiente que arrays u objetos comunes.

      Set almacena solo valores únicos sin claves, mientras que Map asocia claves a valores. En Map puedes usar cualquier tipo de dato como clave; en Set, se garantiza que no haya duplicados.

      Un Set se crea con new Set() y se agregan elementos con .add(valor). Ejemplo: const miSet = new Set(); miSet.add(1); miSet.add("hola");. No se permiten valores duplicados, se ignoran automáticamente.

      Un Map se crea con new Map() y se agregan elementos con .set(clave, valor). Ejemplo: const miMapa = new Map(); miMapa.set('a', 1);. Las claves pueden ser objetos, funciones, números o strings.

      En Set, se usa .has(valor) para comprobar si contiene un valor. En Map, se usa .has(clave) para verificar si una clave existe. Ambos métodos devuelven true o false.

      Set es útil para eliminar duplicados fácilmente y verificar pertenencia con eficiencia. Map ofrece mejor rendimiento en búsquedas y permite claves de cualquier tipo, a diferencia de objetos, que limitan las claves a strings o símbolos.

      Usa Set cuando necesites almacenar una colección de valores únicos y verificar rápidamente si un valor existe. Usa Map cuando necesites asociar claves a valores y acceder a ellos de forma rápida, especialmente si las claves no son strings.

      Deja un comentario

      Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *