# Map and set, weakmap and weakset

### Map

Very similar to an object, however, with object the only key that is allowed is a String. Map on the other hand allows keys of any type. Any type as it ANY type, even an object can be used as a key

- `new Map()`: Creates a new empty map
- `map.set(key, value)`: Sets the key-value pair into the map
- `map.get(key)`: Returns the value by the `key`, returns `undefined` if `key` doesn't exist in map
- `map.has(key)`: Returns `true` if the `key` exists, `false` otherwise
- `map.delete(key)`: Removes the key-value pair by the `key`
- `map.clear()`: Removes everything from the map
- `map.size`: Returns the total number of key-value pair

The way that `Map` compares keys is roughly the same as `===`, but `NaN` is considered to be equal to `NaN` hence, even `NaN` can be used as key as well!

##### Iteration over Map

Three ways of iterating over a map

1. `map.keys()`: Returns an iterable for keys
2. `map.values()`: Returns an iterable for values
3. `map.entries()`: Returns an iterable for both key and value, this is the default for `for ... of`

```javascript
let recipeMap = new Map();
recipeMap.set('cucumber', 500)
	.set('tomatoes', 350)
    .set('onion', 50);

for (let vegs of recipeMap.keys()) {
	console.log(vegs); // cucumber, tomatoes, onion
}

for (let amount of recipeMap.values()) {
	console.log(amount); //500, 350, 50
}

for (let entry of recipeMap) {
	console.log(entry) // [cucumber, 500], [tomatoes, 350], [onion, 50]
}
```

The iteration follows the insertion order, unlike object which doesn't preserve the insertion order in iteration.

##### Map from array

You can create a map from a 2D array like below:

```javascript
let map = new Map([
	['1', 'str1'],
    [1, 'num1'],
    [true, true]
])
```

##### Map from object

You can create a map from an object like below:

```javascript
let obj = {
	name: "john",
    age: 30
}

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

`Object.entries(obj)` will return a 2D array where the 1D array will be the two key-value properties. Since all of the key in object are String the key will always be a String.

##### Object from Map

`Object.fromEntries` does the opposite, it will create an object from a map. All of the key from the map will be converted to a String, because keep in mind that object can only take String as it's key, nothing else. The values will be kept as the same.

```javascript
let map3 = new Map();
map3.set(1, "50");
map3.set("name", "Ricky");
map3.set("2", "Ricky");

let obj = Object.fromEntries(map3); // {"1": "50", "name": "Ricky", "2": "Ricky}.
```

### Set

A set of unique values. There is no key-value pair mapping, `Set` only contains the values, and the same value may only occur once.

- `new Set([iterable])`: To create a set, if the `iterable` object is provided, it creates a set from those values
- `set.add(value)`: Add value to the set, and return the set itself
- `set.delete(value)`: Remove the value, returns `true` if `value` existed otherwise `false`
- `set.has(value)`: `true` if it contains the `value` `false` otherwise.
- `set.clear()`: Removes everything from the set
- `set.size`: Returns the number of elements in the set

##### Set iteration

You can iterate over a set using same `for ... of` loop.

1. `set.keys()`: Return the iterable object for values
2. `set.values()`: This is the same as `set.keys()`
3. `set.entries()`: Return iterable object with entries `[value, value]`

These method exists in order to be compatible with `Map` if you decide to switch from one to the other.

### WeakMap

With normal `Map` the object that is mapped as the value will be kept in memory and so long as the `Map` exists, the object will exist as well.

`WeakMap` on the other hand is different in handling how the garbage collection work. It doesn't prevent garbage-collection of key objects like `Map` does, hence their use cases are completely different.

Here is how `WeakMap` works.

```javascript
let weakmap = new WeakMap();

let obj = {};

weakmap.set(obj, "ok");
```

First of all, `WeakMap` can only take object as the key, if you try to use a primitive like String or integer it will result in an error.

Then, if the object we used as the key have no other references to that object (excluding the one from `WeakMap`) it will be removed from memory automatically.

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

let weakmap = new WeakMap();
weakmap.set(john, "...");

john = null; // the object now lost the reference!

// john is now lost from memory, because WeakMap doesn't prevent garbage collection of key objects.
```

In addition, there are limited functionality to `WeakMap`, there is no iteration, no way to get all keys or values from it.

It only supports `set, get, delete, has` method like a normal map.

##### Use cases

One of the use cases for `WeakMap` is for caching. The result from a function call associated with an object can be stored in a `WeakMap`, future calls on the same object can reuse the same result. Then when you want to clean up the `cache`, you can just delete the reference to the object and that result in `WeakMap` cache will be automatically removed from memory since it gets garbage collected.

### WeakSet

Behaves similarly, but you can only ad objects to `WeakSet` no primitives. Again only supports `add, has, delete` no way of doing iterations.

Used for a yes/no fact, say if an object is still being used somewhere else or not.

The object will also be removed from the `WeakSet` once the object becomes inaccessible/unreachable.