## 30.映射(`Map`)
> 原文: [http://exploringjs.com/impatient-js/ch_maps.html](http://exploringjs.com/impatient-js/ch_maps.html)
>
>貢獻者:[so-hard](https://github.com/so-hard)
### 30.1 使用映射
`Map`的實例將鍵映射到值。單個鍵值映射稱為 `條目`(entry)。
#### 30.1.1 創建映射
創建映射有三種常用方法。
1. 可以使用不帶任何參數的構造函數來創建空 Map:
```js
const emptyMap = new Map();
assert.equal(emptyMap.size, 0);
```
2. 通過給構造函數傳入一個擁有鍵值對的迭代器(例如數組):
```js
const map = new Map([
[ 1, 'one' ],
[ 2, 'two' ],
[ 3, 'three' ], // trailing comma is ignored
]);
```
3. 通過鏈式調用`.set()`方法向 Map 添加`條目`(entry):
```js
const map = new Map()
.set(1, 'one')
.set(2, 'two')
.set(3, 'three');
```
#### 30.1.2 拷貝map
稍后我們可以看到,Map也是一個擁有鍵值對的迭代器,因此你可以使用構造函數來創造一個map的克隆,但僅僅是淺拷貝。
```js
const original = new Map()
.set(false, 'no')
.set(true, 'yes');
const copy = new Map(original);
assert.deepEqual(original, copy);
```
#### 30.1.3 使用單個條目
`.set()` `.get()`分別用來添加成員和讀取值(給定鍵)。
```js
const map = new Map();
map.set('foo', 123);
assert.equal(map.get('foo'), 123);
// 為定義的key:
assert.equal(map.get('bar'), undefined);
//如果條目不存在,則會使返回默認空字符串""
assert.equal(map.get('bar') || '', '');
```
`.has()`檢查 Map 是否具有給定鍵的條目。 `.delete()`刪除條目。
```js
const map = new Map([['foo', 123]]);
assert.equal(map.has('foo'), true);
assert.equal(map.delete('foo'), true)
assert.equal(map.has('foo'), false)
```
#### 30.1.4 得到map的大小和清空map
`.size`返回 Map 中的條目數。 `.clear()`刪除 Map 的所有條目。
```js
const map = new Map()
.set('foo', true)
.set('bar', false)
;
assert.equal(map.size, 2)
map.clear();
assert.equal(map.size, 0)
```
#### 30.1.5 獲取 map 的鍵和值
`.keys()`返回 Map 中`key`的 `iterable`:
```js
const map = new Map()
.set(false, 'no')
.set(true, 'yes')
;
for (const key of map.keys()) {
console.log(key);
}
// Output:
// false
// true
```
我們可以使用 [spread(`...`)](ch_arrays.html#spreading-into-arrays)將`.keys()` 返回的 `iterable` 轉換為數組:
```js
assert.deepEqual(
[...map.keys()],
[false, true]);
```
`.values()`的作用類似于`.keys()`,它返回的是 `value` 的 `iterable` 。
#### 30.1.6 獲取映射的條目
`.entries()`在 Map 上返回一個 `entries` 的 `iterable`:
```js
const map = new Map()
.set(false, 'no')
.set(true, 'yes')
;
for (const entry of map.entries()) {
console.log(entry);
}
// Output:
// [false, 'no']
// [true, 'yes']
```
[Spreading(`...`)](ch_arrays.html#spreading-into-arrays)將`.entries()`返回的 `iterable` 轉換為數組:
```js
assert.deepEqual(
[...map.entrs()],
[[false, 'no'], [true, 'yes']]);
```
Map 實例是 `entry` 上的迭代器。在下面的代碼中,我們使用解構來訪問`map`的鍵和值:
```js
for (const [key, value] of map) {
console.log(key, value);
}
// Output:
// false, 'no'
// true, 'yes'
```
#### 30.1.7 `entries, keys, values` 會按插入的順序羅列出
Map會記錄`entry`創建的順序,`entries, keys,values`遍歷的順序就是插入順序。
```js
const map1 = new Map([
['a', 1],
['b', 2],
]);
assert.deepEqual(
[...map1.keys()], ['a', 'b']);
const map2 = new Map([
['b', 2],
['a', 1],
]);
assert.deepEqual(
[...map2.keys()], ['b', 'a']);
```
#### 30.1.8 對象與Map之間的轉換
只要 `Map` 的鍵是字符串或者 `Symbols` ,你可以將它轉換成對象(通過Object.fromEntries())
```js
const map = new Map([
['a', 1],
['b', 2],
]);
const obj = Object.fromEntries(map);
assert.deepEqual(
obj, {a: 1, b: 2});
```
也可以將對象轉化成 `map` 它的鍵不是字符串就是 `Symbols`
```js
const obj = {
a: 1,
b: 2,
};
const map = new Map(Object.entries(obj));
assert.deepEqual(
map, new Map([['a', 1], ['b', 2]]);
```
### 30.2。示例:計算字符數
`countChars()`返回一個 `map` 會記錄每個字符出現的次數。
```js
function countChars(chars) {
const charCounts = new Map();
for (let ch of chars) {
ch = ch.toLowerCase();
const prevCount = charCounts.get(ch) || 0;
charCounts.set(ch, prevCount+1);
}
return charCounts;
}
const result = countChars('AaBccc');
assert.deepEqual(
[...result],
[
['a', 2],
['b', 1],
['c', 3],
]
);
```
### 30.3 關于 `map` 鍵的更多細節(高級)
任何值都可以當作鍵,甚至是對象:
```js
const map = new Map();
const KEY1 = {};
const KEY2 = {};
map.set(KEY1, 'hello');
map.set(KEY2, 'world');
assert.equal(map.get(KEY1), 'hello');
assert.equal(map.get(KEY2), 'world');
```
#### 30.3.1 什么鍵被認為是一樣的?
大多數 Map 操作需要檢查值是否對應其中一個鍵。它們是通過內部的 [SameValueZero](http://www.ecma-international.org/ecma-262/6.0/#sec-samevaluezero) 來實現的,它的作用類似于`===`,但認為`NaN`等于自身。
因此,您可以在映射中使用`NaN`作為鍵,就像任何其他值一樣:
```js
const map = new Map();
map.set(NaN, 123);
map.get(NaN)
123
```
不同的對象總是被認為是不同的。這是無法配置的東西(但是 - TC39 意識到這是重要的功能)。
```js
new Map().set({}, 1).set({}, 2).size
2
```
### 30.4 Map缺少的方法
#### 30.4.1 fitter和map
你可以在數組上使用 `.map()` 和 `.filter()` ,但 `map` 沒有這樣的方法。解決方案如下:
1. 將 `Map` 轉換為[key,value]的數組。
2. 得到的數組就可以使用 `map` 和 `filter`。
3. 將結果轉為Map。
下面演示它是如何工作的。
```js
const originalMap = new Map()
.set(1, 'a')
.set(2, 'b')
.set(3, 'c');
```
映射 `originalMap`:
```js
const mappedMap = new Map( // step 3
[...originalMap] // step 1
.map(([k, v]) => [k * 2, '_' + v]) // step 2
);
assert.deepEqual([...mappedMap],
[[2,'_a'], [4,'_b'], [6,'_c']]);
```
過濾 `originalMap`:
```js
const filteredMap = new Map( // step 3
[...originalMap] // step 1
.filter(([k, v]) => k < 3) // step 2
);
assert.deepEqual([...filteredMap],
[[1,'a'], [2,'b']]);
```
步驟 1 由擴展運算符(`...`)執行。
#### 30.4.2 組合 `map`
`map` 沒有組合的方法,因此我們需要把 `map` 轉為數組。
讓我們組合下面兩個 `map`:
```js
const map1 = new Map()
.set(1, '1a')
.set(2, '1b')
.set(3, '1c')
;
const map2 = new Map()
.set(2, '2b')
.set(3, '2c')
.set(4, '2d')
;
```
要組合`map1`和`map2`,我們通過擴展運算符(`...`)將它們轉換為數組然后連接這些數組。然后,我們將結果轉換回 Map。所有這一切都在 A 行完成。
```js
const combinedMap = new Map([...map1, ...map2]); // (A)
assert.deepEqual(
[...combinedMap], // convert to Array for comparison
[ [ 1, '1a' ],
[ 2, '2b' ],
[ 3, '2c' ],
[ 4, '2d' ] ]
);
```
 **練習:結合兩張映射**
`exercises/maps-sets/combine_maps_test.js`
### 30.5 快速參考:`Map<K,V>`
注意:為了簡潔起見,我假裝所有鍵具有相同的類型`K`并且所有值具有相同的類型`V`。
#### 30.5.1 構造函數
* `new Map<K, V>(entries?: Iterable<[K, V]>)` <sup>[ES6]</sup>
如果未提供參數`entries`,則會創建空 Map。如果確實提供可迭代的[key,value],那么這些就會被添加到 Map。例如:
```js
const map = new Map([
[ 1, 'one' ],
[ 2, 'two' ],
[ 3, 'three' ], // trailing comma is ignored
]);
···
#### 30.5.2 `Map<K,V>.prototype`:處理單個條目
* `.get(key: K): V` <sup>[ES6]</sup>
返回 Map 中 `key` 映射的 `value`。如果此 Map 中沒有這個 `key`,則返回 `undefined`。
```js
const map = new Map([[1, 'one'], [2, 'two']]);
assert.equal(map.get(1), 'one');
assert.equal(map.get(5), undefined);
```
* `.set(key: K, value: V): this` <sup>[ES6]</sup>
將給定的鍵值對添加到 `map`。如果已存在其鍵為 `key` 的條目,則會更新該條目。否則,將創建一個新條目。該方法會返回 `this`,這意味著您可以鏈接它。
```js
const map = new Map([[1, 'one'], [2, 'two']]);
map.set(1, 'ONE!');
map.set(3, 'THREE!');
assert.deepEqual(
[...map.entries()],
[[1, 'ONE!'], [2, 'two'], [3, 'THREE!']]);
````
* `.has(key: K): boolean` <sup>[ES6]</sup>
返回布爾值,判斷 `Map` 中是否存在該 `key`。
```js
const map = new Map([[1, 'one'], [2, 'two']]);
assert.equal(map.has(1), true); // key exists
assert.equal(map.has(5), false); // key does not exist
```
* `.delete(key: K): boolean` <sup>[ES6]</sup>
如果存在其鍵為`key`的條目,則將其刪除并返回`true`。沒有的話,沒有任何反應,并返回`false`。
```js
const map = new Map([[1, 'one'], [2, 'two']]);
assert.equal(map.delete(1), true);
assert.equal(map.delete(5), false); // nothing happens
assert.deepEqual(
[...map.entries()],
[[2, 'two']]);
```
#### 30.5.3 `Map<K,V>.prototype`:處理所有條目
* `get .size: number` <sup>[ES6]</sup>
返回 `Map` 中的條目數。
```js
const map = new Map([[1, 'one'], [2, 'two']]);
assert.equal(map.size, 2);
```
* `.clear(): void` <sup>[ES6]</sup>
刪除 `Map` 中所有條目。
```js
const map = new Map([[1, 'one'], [2, 'two']]);
assert.equal(map.size, 2);
map.clear();
assert.equal(map.size, 0);
```
#### 30.5.4 `Map<K,V>.prototype`:迭代和循環
迭代和循環都按照條目添加到 Map 的順序發生。
* `.entries(): Iterable<[K,V]>` <sup>[ES6]</sup>
返回一個迭代器包含了所有的鍵值對條目。他們是長度為 2 的數組。
```js
const map = new Map([[1, 'one'], [2, 'two']]);
for (const entry of map.entries()) {
console.log(entry);
}
// Output:
// [1, 'one']
// [2, 'two']
```
* `.forEach(callback: (value: V, key: K, theMap: Map<K,V>) => void, thisArg?: any): void` <sup>[ES6]</sup>
第一個參數是回調函數,對于每個項目都會執行一次回調。如果提供了`thisArg`,則每次回調的執行 `this` 都會指向該它。否則 `this` 為`undefined`。
```js
const map = new Map([[1, 'one'], [2, 'two']]);
map.forEach((value, key) => console.log(value, key));
// Output:
// 'one', 1
// 'two', 2
```
* `.keys(): Iterable<K>` <sup>[ES6]</sup>
返回 Map 中所有鍵名的迭代器。
```js
const map = new Map([[1, 'one'], [2, 'two']]);
for (const key of map.keys()) {
console.log(key);
}
// Output:
// 1
// 2
```
* `.values(): Iterable<V>` <sup>[ES6]</sup>
返回此 Map 中所有鍵值的迭代器。
```js
const map = new Map([[1, 'one'], [2, 'two']]);
for (const value of map.values()) {
console.log(value);
}
// Output:
// 'one'
// 'two'
```
* `[Symbol.iterator](): Iterable<[K,V]>` <sup>[ES6]</sup>
迭代映射的默認方式。與`.entries()`相同。
```js
const map = new Map([[1, 'one'], [2, 'two']]);
for (const [key, value] of map) {
console.log(key, value);
}
// Output:
// 1, 'one'
// 2, 'two'
```
#### 30.5.5 來源
* [TypeScript ](https://github.com/Microsoft/TypeScript/blob/master/lib/)
### 30.6 常問問題
#### 30.6.1 什么時候使用 `Map`,什么時候使用 `對象`?
如果你需要字典這樣的數據結構,他的鍵值不是 `字符串` 也不是`symbol`,那么你不得不選擇 `Map`
但是,如果要將 `字符串` 和 `symbol`作為鍵值的話,則必須決定是否使用 `對象`。下面是一些簡單的通用指南:
* 是否有一組固定不變的鍵(在開發時已知)?
那么你可以通過不變的鍵來訪問對象的值
```js
const value = obj.key
```
* 有一組鍵在程序執行的時候會改變?
然后使用 Map 并通過存儲在變量中的鍵訪問值:
```js
const theKey = 123;
map.get(theKey);
```
#### 30.6.2 什么時候該用 `object` 作為 `map` 的 `key` ?
您通常希望按值比較Map鍵(如果它們具有相同的內容,則認為兩個鍵相等)。這不包括對象。但是這個有一個用例可以將對象作為鍵:外部的數據跟對象有聯系。但 `WeakMaps` 更好地服務于該用例,`WeakMaps` 條目不會阻止 `key` 被垃圾回收機制回收(詳情請參閱下一章)。
#### 30.6.3 為什么 `Map` 會保留條目插入的順序
原則上來說, `Maps` 是無序的。排序條目的主要原因是列出條目,鍵或值的操作是確定性的。這對于測試是非常有用的。
#### 30.6.4?為什么 `map` 有sizes屬性,數組卻是length屬性
在 `Javascript` 中,可索引序列(像數組)有 `length` 屬性,無序集合(如 `Map`)有 `size`屬性。
 **測驗**
參見[測驗應用程序](https://exploringjs.com/impatient-js/ch_quizzes-exercises.html#quizzes)。
- I.背景
- 1.關于本書(ES2019 版)
- 2.常見問題:本書
- 3. JavaScript 的歷史和演變
- 4.常見問題:JavaScript
- II.第一步
- 5.概覽
- 6.語法
- 7.在控制臺上打印信息(console.*)
- 8.斷言 API
- 9.測驗和練習入門
- III.變量和值
- 10.變量和賦值
- 11.值
- 12.運算符
- IV.原始值
- 13.非值undefined和null
- 14.布爾值
- 15.數字
- 16. Math
- 17. Unicode - 簡要介紹(高級)
- 18.字符串
- 19.使用模板字面值和標記模板
- 20.符號
- V.控制流和數據流
- 21.控制流語句
- 22.異常處理
- 23.可調用值
- VI.模塊化
- 24.模塊
- 25.單個對象
- 26.原型鏈和類
- 七.集合
- 27.同步迭代
- 28.數組(Array)
- 29.類型化數組:處理二進制數據(高級)
- 30.映射(Map)
- 31. WeakMaps(WeakMap)
- 32.集(Set)
- 33. WeakSets(WeakSet)
- 34.解構
- 35.同步生成器(高級)
- 八.異步
- 36. JavaScript 中的異步編程
- 37.異步編程的 Promise
- 38.異步函數
- IX.更多標準庫
- 39.正則表達式(RegExp)
- 40.日期(Date)
- 41.創建和解析 JSON(JSON)
- 42.其余章節在哪里?