# Map, Set, WeakMap and WeakSet

## Map

map 跟 object 一樣，唯一不同的是 key 可以是任何類型的值。map 的方法如下：

* `new Map()` – creates the map.
* `map.set(key, value)` – stores the value by the key.
* `map.get(key)` – returns the value by the key, `undefined` if `key` doesn’t exist in map.
* `map.has(key)` – returns `true` if the `key` exists, `false` otherwise.
* `map.delete(key)` – removes the value by the key.
* `map.clear()` – clears the map
* `map.size` – returns the current element count.

```javascript
let map = new Map();

map.set('1', 'str1');   // a string key
map.set(1, 'num1');     // a numeric key
map.set(true, 'bool1'); // a boolean key

// remember the regular Object? it would convert keys to string
// Map keeps the type, so these two are different:
alert( map.get(1)   ); // 'num1'
alert( map.get('1') ); // 'str1'
alert( map.size ); // 3

// object key
let john = { name: "John" };
// for every user, let's store their visits count
let visitsCountMap = new Map();
// john is the key for the map
visitsCountMap.set(john, 123);
alert( visitsCountMap.get(john) ); // 123

// object with object key
let john = { name: "John" };
let visitsCountObj = {}; // try to use an object
visitsCountObj[john] = 123; // try to use john object as the key
// That's what got written!
alert( visitsCountObj["[object Object]"] ); // 123

// before map exist
// we add the id field
let john = { name: "John", id: 1 };
let visitsCounts = {};
// now store the value by id
visitsCounts[john.id] = 123;
alert( visitsCounts[john.id] ); // 123

// chain 
map.set('1', 'str1')
  .set(1, 'num1')
  .set(true, 'bool1');
```

## Map from Object

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

// Object.entries() returns [ ["name","John"], ["age", 30] ]
let map = new Map(Object.entries({
  name: "John",
  age: 30
}));
```

## Iteration over Map

* `map.keys()` – returns an iterable for keys,
* `map.values()` – returns an iterable for values,
* `map.entries()` – returns an iterable for entries `[key, value]`, it’s used by default in `for..of`.

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

// iterate over keys (vegetables)
for (let vegetable of recipeMap.keys()) {
  alert(vegetable); // cucumber, tomatoes, onion
}

// iterate over values (amounts)
for (let amount of recipeMap.values()) {
  alert(amount); // 500, 350, 50
}

// iterate over [key, value] entries
for (let entry of recipeMap) { // the same as of recipeMap.entries()
  alert(entry); // cucumber,500 (and so on)
}

// map 有內建 forEach 方法
// runs the function for each (key, value) pair
recipeMap.forEach( (value, key, map) => {
  alert(`${key}: ${value}`); // cucumber: 500 etc
});
```

## Set

set 是一個值的集合，裡面的值不能重複。替代方案是插入值時用 arr.find() 檢查是否有該值，但會造成性能變差。

* `new Set(iterable)` – creates the set, and if an `iterable` object is provided (usually an array), copies values from it into the set.
* `set.add(value)` – adds a value, returns the set itself.
* `set.delete(value)` – removes the value, returns `true` if `value` existed at the moment of the call, otherwise `false`.
* `set.has(value)` – returns `true` if the value exists in the set, otherwise `false`.
* `set.clear()` – removes everything from the set.
* `set.size` – is the elements count.

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

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

// visits, some users come multiple times
set.add(john);
set.add(pete);
set.add(mary);
set.add(john);
set.add(mary);

// set keeps only unique values
alert( set.size ); // 3

for (let user of set) {
  alert(user.name); // John (then Pete and Mary)
}
```

## Iteration over Set

* `set.keys()` – returns an iterable object for values,
* `set.values()` – same as `set.keys`, for compatibility with `Map`,
* `set.entries()` – returns an iterable object for entries `[value, value]`, exists for compatibility with `Map`.

```javascript
let set = new Set(["oranges", "apples", "bananas"]);

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

// the same with forEach:
set.forEach((value, valueAgain, set) => {
  alert(value);
});
```

## WeakMap and WeakSet

WeakMap / WeakSet 不會阻止 JavaScript 清除 key 的記憶體。換句話說當 key 不存在，存在同個屬性的資料也會不見，使用在當物件存在的時候才需要的資料，物件消失資料也消失。

```javascript
// delete object 
let john = { name: "John" };
// the object can be accessed, john is the reference to it
// overwrite the reference
john = null;
// the object will be removed from memory

// store in array object still exist
let john = { name: "John" };
let array = [ john ];
john = null; // overwrite the reference
// john is stored inside the array, so it won't be garbage-collected
// we can get it as array[0]

// store in map object still exict
let john = { name: "John" };
let map = new Map();
map.set(john, "...");
john = null; // overwrite the reference
// john is stored inside the map,
// we can get it by using map.keys()

// weakMap / weakSet 只能使用 object 當 key 值
let weakMap = new WeakMap();
let obj = {};
weakMap.set(obj, "ok"); // works fine (object key)
// can't use a string as the key
weakMap.set("test", "Whoops"); // Error, because "test" is not an object

// store in weakMap object desroty
let john = { name: "John" };
let weakMap = new WeakMap();
weakMap.set(john, "...");
john = null; // overwrite the reference
// john is removed from memory!

// 如果物件被移除，儲存值也會被移除
weakMap.set(john, "secret documents");
// if john dies, secret documents will be destroyed automatically

// 手動清理不要的物件
let john = { name: "John" };
// map: user => visits count
let visitsCountMap = new Map();
// john is the key for the map
visitsCountMap.set(john, 123);
// now john leaves us, we don't need him anymore
john = null;
// but it's still in the map, we need to clean it!
alert( visitsCountMap.size ); // 1
// and john is also in the memory, because Map uses it as the key

// weakMap 當物件消失值也會消失
let john = { name: "John" };
let visitsCountMap = new WeakMap();
visitsCountMap.set(john, 123);
// now john leaves us, we don't need him anymore
john = null;
// there are no references except WeakMap,
// so the object is removed both from the memory and from visitsCountMap automatically

// weakSet exaample
let messages = [
    {text: "Hello", from: "John"},
    {text: "How goes?", from: "John"},
    {text: "See you soon", from: "Alice"}
];
// fill it with array elements (3 items)
let unreadSet = new WeakSet(messages);
// use unreadSet to see whether a message is unread
alert(unreadSet.has(messages[1])); // true
// remove it from the set after reading
unreadSet.delete(messages[1]); // true
// and when we shift our messages history, the set is cleaned up automatically
messages.shift();
// no need to clean unreadSet, it now has 2 items
// (though technically we don't know for sure when the JS engine clears it)
```

weakMap 只有以下方法：

* `weakMap.get(key)`
* `weakMap.set(key, value)`
* `weakMap.delete(key)`
* `weakMap.has(key)`

weakSet&#x20;

* It is analogous to `Set`, but we may only add objects to `WeakSet` (not primitives).
* An object exists in the set while it is reachable from somewhere else.
* Like `Set`, it supports `add`, `has` and `delete`, but not `size`, `keys()` and no iterations.
