Javascript: Map e Set
Tags: javascript, tutorial, programming
Published: 2022-12-29 20:10:08

Sabemos que os objetos, são usados para armazenar coleções codificadas e arrays são usados para armazenar coleções ordenadas. Mas isso não é suficiente para a vida real. É por isso que aprenderemos sobre `Map` e `Set`.

## Map

Map é uma coleção de itens de dados com chave, assim como um arquivo `Object`. Mas a principal diferença, é que `Map` permite chaves de qualquer tipo.

Métodos e propeidades são:

- `new Map()` - cria o map.
- map.set(key, value) - armazena o valor pela chave.
- map.get(key) -  retorna o valor pela chave e undefined, caso a key não exista no map.
- map.has(key) - retorna true se a key existir, false caso contrário.
- map.delete(key) - remove o elemento (o par de chave/valor) pela chave.
- map.clear() - remove tudo do map.
- map.size - retorna a contagem do elemento atual.

Por exemplo:

```jsx
let map = new Map()

`map.set('1', 'str1')` // uma key string
`map.set(1, 'num1')` // uma key numérica
`map.set(true, 'bool1')` // uma key booleana

// lembra do objeto regular? converteria as chaves em string
// O map mantém o tipo, então esses dois são diferentes:

alert( map.get(1)   ); // 'num1'
alert( map.get('1') ); // 'str1'

alert( map.size ); // 3

```
Como podemos ver, ao contrário dos objetos, as chaves não são convertidas em strings. Qualquer tipo de chave é possível.

### map[key] não é a maneira certa de usar um `Map`

Embora `map[key]` também funcione, por exemplo, podemos definir `map[key] = 2`, isso é tratar o map como um objeto JavaScript simples, portanto, implica todas as limitações correspondentes (apenas chaves de strings/símbolo e assim por diante).

### O map também pode usar objetos como chaves

Por exemplo:

```jsx
let john = { name: "John" };

// para cada usuário, vamos armazenar a contagem de visitas
let visitsCountMap = new Map();

// john é a chave para o map
visitsCountMap.set(john, 123);

alert( visitsCountMap.get(john) ); // 123
```
Usar objetos como chaves, é um dos recursos mais notáveis e importantes do `Map`. O mesmo não conta para Object. String como uma chave em `Object` está correto, mas não podemos usar outro `Object` como uma chave em `Object`.

Vamos tentar:

```jsx
let john = { name: "John" };
let ben = { name: "Ben" };

let visitsCountObj = {}; // tente usar um objeto

visitsCountObj[ben] = 234; // tente usar o objeto ben como a chave
visitsCountObj[john] = 123; // tente usar o objeto john como a chave, o objeto ben será substituído

// Isso é o que está retornando!
alert( visitsCountObj["[object Object]"] ); // 123
```
Como `visitsCountObj` é um objeto, ele converte todas as chaves `Object`, como john e ben acima, na mesma string "[object Object]". Definitivamente não é o que queremos.

## Iteração sobre o map

Para fazer um loop sobre um map, existem 3 métodos:

- `map.keys()` - retorna um iterável para chaves,
- `map.values()` - retorna um iterável para valores,
- `map.entries()` - retorna um iterável para `[keys, value]`, é usado por padrão em `for...of`.

Por exemplo:

```jsx
let recipeMap = new Map([
  ['cucumber', 500],
  ['tomatoes', 350],
  ['onion',    50]
]);

// iterar sobre chaves (vegetais)
for (let vegetable of recipeMap.keys()) {
  alert(vegetable); // cucumber, tomatoes, onion
}

// iterar sobre valores (quantidades)
for (let amount of recipeMap.values()) {
  alert(amount); // 500, 350, 50
}

// iterar sobre valores [key, value] 
for (let entry of recipeMap) { // o mesmo de recipeMap.entries()
  alert(entry); // pepino, 500 (e assim por diante)
}
```
### A ordem de iserção é usada

A iteração ocorre na mesma ordem em que os valores foram inseridos. Map preserva essa ordem, ao contrário de um `Object`.

Além disso, `Map` possui um método `forEach` embutido, semelhante a um Array:

```jsx
// executa a função para cada par (key, value)
recipeMap.forEach( (value, key, map) => {
  alert(`${key}: ${value}`); // pepino: 500 etc
});
```
## Object.entries: Map from Object

Quando um Map é criado, podemos passar um array (ou outro iterável) com pares chave/valor para inicialização, assim:

```jsx
// array of [key, value] pairs
let map = new Map([
  ['1',  'str1'],
  [1,    'num1'],
  [true, 'bool1']
]);

alert( map.get('1') ); // str1
```
Se tivermos um objeto simples e quisermos criar um Map a partir dele, podemos usar o método integrado `Object.entries(obj)` que retorna uma array de pares de chave/valor para um objeto exatamente nesse formato.

Assim, podemos criar um map a partir de um objeto como esse:

```jsx
let obj = {
  name: "John",
  age: 30
};

let map = new Map(Object.entries(obj));

alert( map.get('name') ); // John
```
Aqui, `Object.entries` retorna o array de pares chave/valor: [ `["name","John"], ["age", 30] ]`. Isso é o que Mapprecisa.

## Object.fromEntries: Object from Map

Acabamos de ver como criar Map a partir de um objeto simples com Object.entries(obj). Existe o método Object.fromEntries que faz o contrário: dado um array de pares [chave, valor], ele cria um objeto a partir deles:

```jsx
let prices = Object.fromEntries([
  ['banana', 1],
  ['orange', 2],
  ['meat', 4]
]);

// now prices = { banana: 1, orange: 2, meat: 4 }

alert(prices.orange); // 2
```

Podemos usar `Object.fromEntries` para obter um objeto simples de Map. Por exemplo, armazenamos os dados em um map, mas precisamos passá-los para um código de terceiros que espera um objeto simples. Aqui vamos nós:

```jsx
let map = new Map();
map.set('banana', 1);
map.set('orange', 2);
map.set('meat', 4);

let obj = Object.fromEntries(map.entries()); // make a plain object (*)

// done!
// obj = { banana: 1, orange: 2, meat: 4 }

alert(obj.orange); // 2
```
Uma chamada para `map.entries()` retorna um iterável de pares chave/valor, exatamente no formato correto para Object.fromEntries. Também poderíamos tornar a linha (*) mais curta:

```jsx
let obj = Object.fromEntries(map); // omit .entries()
```
Isso é o mesmo, porque Object.fromEntries espera um objeto iterável como argumento. Não necessariamente uma array. E a iteração padrão para map, retorna os mesmos pares chave/valor de `map.entries()`. Assim, obtemos um objeto simples com os mesmos valores/chave do mapa.

## Set

Uma `Set` é uma coleção de tipo especial – “conjunto de valores” (sem chaves), onde cada valor pode ocorrer apenas uma vez.

Seus principais métodos são:

- `new Set([iterable])` – cria o conjunto e, se um objeto iterable for fornecido (geralmente um array), copia os valores dele para o conjunto.
- `set.add(value)` – adiciona um valor, retorna o próprio conjunto.
- `set.delete(value)` – remove o valor, retorna true se value existir no momento da chamada, caso contrário false.
- `set.has(value)` – retorna true se o valor existir no conjunto, caso contrário false.
- `set.clear()` – remove tudo do conjunto.
- `set.size` – é a contagem de elementos.

A principal característica é que chamadas repetidas de `set.add(value)` com o mesmo valor não fazem nada. É por isso que cada valor `Set` aparece apenas uma vez.

Por exemplo, temos visitantes chegando e gostaríamos de lembrar de todos. Mas visitas repetidas não devem levar a duplicatas. Um visitante deve ser “contado” apenas uma vez. Por exemplo;

```jsx
let set = new Set();

let john = { name: "John" };
let pete = { name: "Pete" };
let mary = { name: "Mary" };

// visitas, alguns usuários vêm várias vezes
set.add(john);
set.add(pete);
set.add(mary);
set.add(john);
set.add(mary);

// set mantém apenas valores únicos
alert( set.size ); // 3

for (let user of set) {
  alert(user.name); // John (então Pete e Mary)
}
```
Uma alternativa a Set pode ser uma array de usuários e o código para verificar se há duplicatas em cada inserção usando `arr.find`. Mas o desempenho seria muito pior, porque esse método percorre todo o array verificando cada elemento. Set é muito melhor otimizado internamente para verificações de exclusividade.

## Iteração sobre Conjunto

Podemos percorrer um conjunto com `for..of` ou usando `forEach`:

```JSX
let set = new Set(["laranjas", "maçãs", "bananas"]);

for (let value of set) alert(value);

// o mesmo com forEach:
set.forEach((value, valueAgain, set) => {
  alert(value);
});
```
Observe uma coisa engraçada. A função de retorno de chamada de  `forEach` possui 3 argumentos: um value, o mesmo valor `valueAgain` e, em seguida, o objeto de destino. De fato, o mesmo valor aparece nos argumentos duas vezes.

Isso é para compatibilidade com Map onde o callback passado `forEach` tem três argumentos. Parece um pouco estranho, com certeza. Mas isso pode ajudar a substituir Map com Set com facilidade em certos casos e vice-versa.

Os mesmos métodos Mappara iteradores também são suportados:

- `set.keys()` – retorna um objeto iterável para valores,
- `set.values()` – o mesmo que set.keys(), para compatibilidade com Map, 
- `set.entries()` – retorna um objeto iterável para entradas [value, value], existe para compatibilidade com Map.

## Resumo

Map – é uma coleção de valores chaveados.

Métodos e propriedades:

- `new Map([iterable])` – cria o map, com opcional iterable(por exemplo, array) de pares [key,value] para inicialização.
- `map.set(key, value)` – armazena o valor pela chave, retorna o próprio map.
- `map.get(key)` – retorna o valor pela chave, undefined caso key não exista no map.
- `map.has(key)` – retorna true se a key existir, false caso contrário.
- `map.delete(key)` – remove o elemento pela chave, retorna true se key existe no momento da chamada, caso contrário false.
- `map.clear()` – remove tudo do map.
-` map.size` – retorna a contagem do elemento atual.

As diferenças de um regular Object:

Quaisquer chaves, objetos podem ser chaves.
Métodos convenientes adicionais, uma propriedade size.
Set – é uma coleção de valores únicos.

Métodos e propriedades:

- `new Set([iterable])` – cria o conjunto, com valores opcionais iterable(por exemplo, array) para inicialização.
- `set.add(value)` – adiciona um valor (não faz nada se value existir), retorna o próprio conjunto.
`set.delete(value)` – remove o valor, retorna true se value existir no momento da chamada, caso contrário false.
- `set.has(value)` – retorna true se o valor existir no conjunto, caso contrário false.
- `set.clear()` – remove tudo do conjunto.
- `set.size` – é a contagem de elementos.