到目前為止,我們已經學習了以下複雜的資料結構
- 物件用於儲存鍵控集合。
- 陣列用於儲存順序集合。
但這還不足以應付實際情況。這就是為什麼 Map
和 Set
也存在的緣故。
Map
Map 是一個鍵值資料項目的集合,就像一個 Object
。但主要的不同點是 Map
允許任何類型的鍵。
方法和屬性為
new Map()
– 建立地圖。map.set(key, value)
– 儲存鍵的值。map.get(key)
– 傳回鍵的值,如果key
不存在於地圖中,則傳回undefined
。map.has(key)
– 如果key
存在,則傳回true
,否則傳回false
。map.delete(key)
– 移除鍵的元素(鍵/值對)。map.clear()
– 移除地圖中的所有東西。map.size
– 傳回目前的元素計數。
例如
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
正如我們所見,與物件不同,鍵不會轉換成字串。任何類型的鍵都是可能的。
map[key]
不是使用 Map
的正確方法雖然 map[key]
也能運作,例如我們可以設定 map[key] = 2
,這將 map
視為一個純粹的 JavaScript 物件,因此它暗示所有對應的限制(僅字串/符號鍵等)。
所以我們應該使用 map
方法:set
、get
等。
Map 也可以使用物件作為鍵。
例如
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
使用物件作為鍵是 Map
最顯著且重要的功能之一。Object
沒有這個功能。字串作為 Object
中的鍵是可以的,但我們無法使用另一個 Object
作為 Object
中的鍵。
讓我們試試看
let john = { name: "John" };
let ben = { name: "Ben" };
let visitsCountObj = {}; // try to use an object
visitsCountObj[ben] = 234; // try to use ben object as the key
visitsCountObj[john] = 123; // try to use john object as the key, ben object will get replaced
// That's what got written!
alert( visitsCountObj["[object Object]"] ); // 123
由於 visitsCountObj
是個物件,它會將所有 Object
鍵,例如上述的 john
和 ben
,轉換成相同的字串 "[object Object]"
。這絕對不是我們想要的。
Map
如何比較鍵為了測試鍵的等價性,Map
使用演算法 SameValueZero。它大致與嚴格相等 ===
相同,但不同之處在於 NaN
被視為等於 NaN
。因此 NaN
也可用作鍵。
此演算法無法變更或自訂。
每個 map.set
呼叫都會傳回地圖本身,因此我們可以「串接」呼叫
map.set('1', 'str1')
.set(1, 'num1')
.set(true, 'bool1');
迭代 Map
對於迴圈 map
,有 3 個方法
map.keys()
– 傳回一個可迭代的鍵值,map.values()
– 傳回一個可迭代的值,map.entries()
– 傳回一個可迭代的條目[key, value]
,預設用於for..of
。
例如
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
會保留此順序,與一般 Object
不同。
此外,Map
有內建的 forEach
方法,類似於 Array
// runs the function for each (key, value) pair
recipeMap.forEach( (value, key, map) => {
alert(`${key}: ${value}`); // cucumber: 500 etc
});
Object.entries:從物件建立 Map
建立 Map
時,我們可以傳入一個陣列(或其他可迭代物件)包含鍵值對進行初始化,如下所示
// array of [key, value] pairs
let map = new Map([
['1', 'str1'],
[1, 'num1'],
[true, 'bool1']
]);
alert( map.get('1') ); // str1
如果我們有一個純粹的物件,我們想要從中建立一個 Map
,那麼我們可以使用內建方法 Object.entries(obj),它會傳回一個陣列,其中包含物件的鍵值對,格式完全相同。
因此,我們可以從一個物件建立一個 map,如下所示
let obj = {
name: "John",
age: 30
};
let map = new Map(Object.entries(obj));
alert( map.get('name') ); // John
在此,Object.entries
傳回鍵值對陣列:[ ["name","John"], ["age", 30] ]
。這就是 Map
所需的。
Object.fromEntries:從 Map 建立物件
我們剛剛看到如何使用 Object.entries(obj)
從純粹的物件建立 Map
。
有一個 Object.fromEntries
方法可以執行相反的動作:給定一個 [key, value]
對陣列,它會從中建立一個物件
let prices = Object.fromEntries([
['banana', 1],
['orange', 2],
['meat', 4]
]);
// now prices = { banana: 1, orange: 2, meat: 4 }
alert(prices.orange); // 2
我們可以使用 Object.fromEntries
從 Map
取得一個純粹的物件。
例如,我們將資料儲存在 Map
中,但我們需要將其傳遞給預期純粹物件的第三方程式碼。
如下所示
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
呼叫 map.entries()
會傳回一個鍵值對可迭代物件,格式完全符合 Object.fromEntries
的需求。
我們也可以縮短第 (*)
行
let obj = Object.fromEntries(map); // omit .entries()
這是一樣的,因為 Object.fromEntries
預期一個可迭代物件作為參數。不一定是陣列。而 map
的標準迭代會傳回與 map.entries()
相同的鍵值對。因此,我們會取得一個純粹的物件,其鍵值與 map
相同。
Set
一個 Set
是一個特殊的集合類型 - 「值集合」(沒有鍵),其中每個值只能出現一次。
它的主要方法是
new Set([iterable])
– 建立集合,如果提供了iterable
物件(通常是陣列),則從中複製值到集合中。set.add(value)
– 新增一個值,傳回集合本身。set.delete(value)
– 移除該值,如果value
在呼叫時存在,則傳回true
,否則傳回false
。set.has(value)
– 如果值存在於集合中,則傳回true
,否則傳回false
。set.clear()
– 從集合中移除所有項目。set.size
– 是元素數量。
主要特色是重複呼叫 set.add(value)
時,如果值相同,則不會執行任何動作。這就是為什麼每個值只會在 Set
中出現一次的原因。
例如,我們有訪客到來,我們想要記住每個人。但重複拜訪不應該導致重複資料。訪客必須只被「計算」一次。
Set
正好適合這個需求
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)
}
Set
的替代方案可能是使用者陣列,以及使用 arr.find 在每次插入時檢查重複資料的程式碼。但效能會差很多,因為這個方法會遍歷整個陣列,檢查每個元素。Set
在內部針對唯一性檢查進行了更好的最佳化。
遍歷 Set
我們可以使用 for..of
或 forEach
來迴圈遍歷 Set
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);
});
請注意一個有趣的事情。傳遞給 forEach
的回呼函式有 3 個參數:一個 value
,然後同一個值 valueAgain
,然後是目標物件。的確,同一個值會出現在參數中兩次。
這是為了與 Map
相容,其中傳遞給 forEach
的回呼函式有三個參數。看起來有點奇怪,這點可以確定。但在某些情況下,這可能有助於輕鬆地用 Set
取代 Map
,反之亦然。
Map
針對反覆運算子所使用的相同方法也受到支援
set.keys()
– 傳回值的可迭代物件,set.values()
– 與set.keys()
相同,用於與Map
相容,set.entries()
– 傳回條目的可迭代物件[value, value]
,用於與Map
相容。
摘要
Map
– 是鍵值集合。
方法和屬性
new Map([iterable])
– 建立 Map,並提供初始化用的[key,value]
成對可迭代物件(例如陣列)。map.set(key, value)
– 根據鍵值儲存值,傳回 Map 本身。map.get(key)
– 傳回鍵的值,如果key
不存在於地圖中,則傳回undefined
。map.has(key)
– 如果key
存在,則傳回true
,否則傳回false
。map.delete(key)
– 根據鍵值移除元素,如果在呼叫時key
存在,則傳回true
,否則傳回false
。map.clear()
– 移除地圖中的所有東西。map.size
– 傳回目前的元素計數。
與一般 Object
的差異
- 任何鍵值,物件都可以是鍵值。
- 額外方便的方法,
size
屬性。
Set
- 是唯一值的集合。
方法和屬性
new Set([iterable])
- 建立集合,可選擇iterable
(例如陣列) 的值來初始化。set.add(value)
- 加入一個值 (如果value
存在則不執行任何動作),回傳集合本身。set.delete(value)
– 移除該值,如果value
在呼叫時存在,則傳回true
,否則傳回false
。set.has(value)
– 如果值存在於集合中,則傳回true
,否則傳回false
。set.clear()
– 從集合中移除所有項目。set.size
– 是元素數量。
Map
和 Set
的迭代總是按照插入順序,所以我們不能說這些集合是無序的,但我們不能重新排序元素或直接透過數字取得元素。
留言
<code>
標籤,要插入多行,請用<pre>
標籤包覆,要插入超過 10 行,請使用沙盒 (plnkr、jsbin、codepen…)