Map
)Map<K,V>
Map<K,V>.prototype
:處理單一項目Map<K,V>.prototype
:處理所有項目Map<K,V>.prototype
:反覆處理和迴圈.size
,而陣列有 .length
?在 ES6 之前,JavaScript 沒有字典的資料結構,並將物件(濫)用為從字串到任意值的字典。ES6 帶來了 Map,它是從任意值到任意值的字典。
Map
的一個實例會將鍵對應到值。單一鍵值對應稱為項目。
建立 Map 有三種常見的方式。
首先,你可以使用不帶任何參數的建構函式建立一個空的 Map
const emptyMap = new Map();
.equal(emptyMap.size, 0); assert
其次,你可以傳遞一個可迭代物件(例如陣列)給建構函式,其中包含鍵值「配對」(包含兩個元素的陣列)
const map = new Map([
1, 'one'],
[2, 'two'],
[3, 'three'], // trailing comma is ignored
[; ])
第三,.set()
方法會將項目加入 Map 中,而且可以串連使用
const map = new Map()
.set(1, 'one')
.set(2, 'two')
.set(3, 'three');
正如我們稍後將看到的,Map 也是可迭代的鍵值配對。因此,你可以使用建構函式建立 Map 的副本。這個副本是淺層的:鍵和值是相同的;它們不會被複製。
const original = new Map()
.set(false, 'no')
.set(true, 'yes');
const copy = new Map(original);
.deepEqual(original, copy); assert
.set()
和 .get()
用於寫入和讀取值(給定鍵)。
const map = new Map();
.set('foo', 123);
map
.equal(map.get('foo'), 123);
assert// Unknown key:
.equal(map.get('bar'), undefined);
assert// Use the default value '' if an entry is missing:
.equal(map.get('bar') ?? '', ''); assert
.has()
檢查 Map 是否有具有給定鍵的項目。.delete()
會移除項目。
const map = new Map([['foo', 123]]);
.equal(map.has('foo'), true);
assert.equal(map.delete('foo'), true)
assert.equal(map.has('foo'), false) assert
.size
包含 Map 中項目的數量。.clear()
會移除 Map 中的所有項目。
const map = new Map()
.set('foo', true)
.set('bar', false)
;
.equal(map.size, 2)
assert.clear();
map.equal(map.size, 0) assert
.keys()
會傳回 Map 中鍵的可迭代物件
const map = new Map()
.set(false, 'no')
.set(true, 'yes')
;
for (const key of map.keys()) {
console.log(key);
}// Output:
// false
// true
我們使用 Array.from()
將 .keys()
傳回的可迭代物件轉換為陣列
.deepEqual(
assertArray.from(map.keys()),
false, true]); [
.values()
的運作方式與 .keys()
相同,但處理的是值,而不是鍵。
.entries()
會傳回 Map 中項目的可迭代物件
const map = new Map()
.set(false, 'no')
.set(true, 'yes')
;
for (const entry of map.entries()) {
console.log(entry);
}// Output:
// [false, 'no']
// [true, 'yes']
Array.from()
會將 .entries()
傳回的可迭代物件轉換為陣列
.deepEqual(
assertArray.from(map.entries()),
false, 'no'], [true, 'yes']]); [[
Map 實例也是可迭代的項目。在以下程式碼中,我們使用 解構 來存取 map
的鍵和值
for (const [key, value] of map) {
console.log(key, value);
}// Output:
// false, 'no'
// true, 'yes'
Map 會記錄項目建立的順序,並在列出項目、鍵或值時遵循此順序
const map1 = new Map([
'a', 1],
['b', 2],
[;
]).deepEqual(
assertArray.from(map1.keys()), ['a', 'b']);
const map2 = new Map([
'b', 2],
['a', 1],
[;
]).deepEqual(
assertArray.from(map2.keys()), ['b', 'a']);
只要 Map 只使用字串和符號作為鍵,你就可以將它轉換為物件(透過 Object.fromEntries()
)
const map = new Map([
'a', 1],
['b', 2],
[;
])const obj = Object.fromEntries(map);
.deepEqual(
assert, {a: 1, b: 2}); obj
你也可以將物件轉換為具有字串或符號鍵的 Map(透過 Object.entries()
)
const obj = {
a: 1,
b: 2,
;
}const map = new Map(Object.entries(obj));
.deepEqual(
assert, new Map([['a', 1], ['b', 2]])); map
countChars()
會傳回一個 Map,將字元對應到出現次數。
function countChars(chars) {
const charCounts = new Map();
for (let ch of chars) {
= ch.toLowerCase();
ch const prevCount = charCounts.get(ch) ?? 0;
.set(ch, prevCount+1);
charCounts
}return charCounts;
}
const result = countChars('AaBccc');
.deepEqual(
assertArray.from(result),
['a', 2],
['b', 1],
['c', 3],
[
]; )
任何值都可以是鍵,甚至是物件
const map = new Map();
const KEY1 = {};
const KEY2 = {};
.set(KEY1, 'hello');
map.set(KEY2, 'world');
map
.equal(map.get(KEY1), 'hello');
assert.equal(map.get(KEY2), 'world'); assert
大多數 Map 操作都需要檢查值是否等於其中一個鍵值。它們透過內部操作 SameValueZero 來執行此操作,其運作方式類似於 ===
,但將 NaN
視為等於自身。
因此,你可以將 NaN
用作 Map 中的鍵值,就像任何其他值一樣
> const map = new Map();
> map.set(NaN, 123);
> map.get(NaN)123
不同的物件總是會被視為不同。這是無法變更的事情(目前尚無法變更 – 設定鍵值相等性是 TC39 的長期規劃藍圖)。
> new Map().set({}, 1).set({}, 2).size2
你可以對陣列執行 .map()
和 .filter()
,但 Map 沒有此類操作。解決方案是
我將使用下列 Map 來示範其運作方式。
const originalMap = new Map()
.set(1, 'a')
.set(2, 'b')
.set(3, 'c');
對應 originalMap
const mappedMap = new Map( // step 3
Array.from(originalMap) // step 1
.map(([k, v]) => [k * 2, '_' + v]) // step 2
;
).deepEqual(
assertArray.from(mappedMap),
2,'_a'], [4,'_b'], [6,'_c']]); [[
篩選 originalMap
const filteredMap = new Map( // step 3
Array.from(originalMap) // step 1
.filter(([k, v]) => k < 3) // step 2
;
).deepEqual(Array.from(filteredMap),
assert1,'a'], [2,'b']]); [[
Array.from()
會將任何可迭代物件轉換為陣列。
沒有用於合併 Map 的方法,因此我們必須使用與前一節類似的解決方法。
讓我們合併下列兩個 Map
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
,我們會建立一個新的陣列,並將 map1
和 map2
的項目(鍵值對)散佈(...
)到其中(透過反覆運算)。然後,我們將陣列轉換回 Map。所有這些都在 A 行中完成
const combinedMap = new Map([...map1, ...map2]); // (A)
.deepEqual(
assertArray.from(combinedMap), // convert to Array for comparison
1, '1a' ],
[ [ 2, '2b' ],
[ 3, '2c' ],
[ 4, '2d' ] ]
[ ; )
練習:合併兩個 Map
exercises/maps/combine_maps_test.mjs
Map<K,V>
注意:為了簡潔起見,我假設所有鍵值都有相同的類型 K
,而所有值都有相同的類型 V
。
new Map<K, V>(entries?: Iterable<[K, V]>)
[ES6]
如果你未提供參數 entries
,則會建立一個空的 Map。如果你提供 [鍵值、值] 成對的可迭代物件,則這些成對會新增為 Map 中的項目。例如
const map = new Map([
1, 'one' ],
[ 2, 'two' ],
[ 3, 'three' ], // trailing comma is ignored
[ ; ])
Map<K,V>.prototype
:處理單一項目.get(key: K): V
[ES6]
傳回 key
在此 Map 中對應的 value
。如果在此 Map 中沒有鍵值 key
,則傳回 undefined
。
const map = new Map([[1, 'one'], [2, 'two']]);
.equal(map.get(1), 'one');
assert.equal(map.get(5), undefined); assert
.set(key: K, value: V): this
[ES6]
將指定的鍵值對應到指定的數值。如果已經有鍵值為 key
的項目,則會更新該項目。否則,會建立一個新的項目。此方法會傳回 this
,這表示你可以將其串接起來。
const map = new Map([[1, 'one'], [2, 'two']]);
.set(1, 'ONE!')
map.set(3, 'THREE!');
.deepEqual(
assertArray.from(map.entries()),
1, 'ONE!'], [2, 'two'], [3, 'THREE!']]); [[
.has(key: K): boolean
[ES6]
傳回此 Map 中是否存在指定的 key。
const map = new Map([[1, 'one'], [2, 'two']]);
.equal(map.has(1), true); // key exists
assert.equal(map.has(5), false); // key does not exist assert
.delete(key: K): boolean
[ES6]
如果存在一個 key 為 key
的項目,則會將其移除並傳回 true
。否則,不會執行任何動作並傳回 false
。
const map = new Map([[1, 'one'], [2, 'two']]);
.equal(map.delete(1), true);
assert.equal(map.delete(5), false); // nothing happens
assert.deepEqual(
assertArray.from(map.entries()),
2, 'two']]); [[
Map<K,V>.prototype
:處理所有項目get .size: number
[ES6]
傳回此 Map 中的項目數。
const map = new Map([[1, 'one'], [2, 'two']]);
.equal(map.size, 2); assert
.clear(): void
[ES6]
移除此 Map 中的所有項目。
const map = new Map([[1, 'one'], [2, 'two']]);
.equal(map.size, 2);
assert.clear();
map.equal(map.size, 0); assert
Map<K,V>.prototype
:反覆運算和迴圈反覆運算和迴圈都會按照項目加入 Map 的順序執行。
.entries(): Iterable<[K,V]>
[ES6]
傳回一個可反覆運算的物件,其中包含此 Map 中每個項目的 [key, value] 成對資料。這些成對資料是長度為 2 的陣列。
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
[ES6]
第一個參數是一個回呼函式,會針對此 Map 中的每個項目呼叫一次。如果提供了 thisArg
,則會針對每個呼叫將 this
設為 thisArg
。否則,會將 this
設為 undefined
。
const map = new Map([[1, 'one'], [2, 'two']]);
.forEach((value, key) => console.log(value, key));
map// Output:
// 'one', 1
// 'two', 2
.keys(): Iterable<K>
[ES6]
傳回一個可反覆運算的物件,其中包含此 Map 中的所有 key。
const map = new Map([[1, 'one'], [2, 'two']]);
for (const key of map.keys()) {
console.log(key);
}// Output:
// 1
// 2
.values(): Iterable<V>
[ES6]
傳回一個可反覆運算的物件,其中包含此 Map 中的所有 value。
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]>
[ES6]
反覆運算 Map 的預設方式。與 .entries()
相同。
const map = new Map([[1, 'one'], [2, 'two']]);
for (const [key, value] of map) {
console.log(key, value);
}// Output:
// 1, 'one'
// 2, 'two'
如果你需要一個字典類型的資料結構,其 key 既不是字串也不是符號,那麼你別無選擇:你必須使用 Map。
但是,如果你的 key 是字串或符號,你必須決定是否要使用物件。一個粗略的通用準則如下
是否有一組固定的 key(在開發時已知)?
然後使用物件 obj
,並透過固定的 key 來存取值
const value = obj.key;
key 組是否會在執行時變更?
然後使用 Map map
,並透過儲存在變數中的 key 來存取值
const theKey = 123;
.get(theKey); map
你通常希望 Map key 能夠透過值來比較(如果兩個 key 具有相同的內容,則會視為相等)。這排除了物件。但是,有一種使用案例會將物件作為 key:將資料從外部附加到物件。但使用 WeakMaps 會更適合這種使用案例,其中項目不會阻止 key 被垃圾回收(有關詳細資訊,請參閱 下一章)。
原則上,Map 是無序的。排序項目的主要原因是,列出項目、key 或值的操作是確定性的。例如,這有助於測試。
.size
,而陣列有 .length
?在 JavaScript 中,可索引序列(例如陣列和字串)具有 .length
,而未索引的集合(例如 Map 和 Set)具有 .size
.length
基於索引;它總是最高索引加一。.size
計算集合中的元素數量。 測驗
請參閱 測驗應用程式。