Map
Map 是一個(gè)帶鍵的數(shù)據(jù)項(xiàng)的集合,就像一個(gè) Object 一樣。但是它們最大的差別是 Map 允許任何類型的鍵(key)。
它的方法和屬性如下:
-
new Map()—— 創(chuàng)建 map。 -
map.set(key, value)—— 根據(jù)鍵存儲(chǔ)值。 -
map.get(key)—— 根據(jù)鍵來返回值,如果map中不存在對(duì)應(yīng)的key,則返回undefined。 -
map.has(key)—— 如果key存在則返回true,否則返回false。 -
map.delete(key)—— 刪除指定鍵的值。 -
map.clear()—— 清空 map。 -
map.size—— 返回當(dāng)前元素個(gè)數(shù)。
舉個(gè)例子:
let map = new Map();
map.set('1', 'str1'); //字符串鍵
map.set(1, 'num1'); //數(shù)字鍵
map.set(true, 'bool');//布爾值鍵
//如果是普通的 Object,它i會(huì)將鍵轉(zhuǎn)化為字符串
//Map 則會(huì)保留鍵的類型,所以下面這兩個(gè)結(jié)果不同:
alert( map.get(1)); //'num1'
alert( map.get('1')); //'str1'
alert(map.size); //3
如我們所見,與對(duì)象不同,鍵不會(huì)被轉(zhuǎn)換成字符串。鍵可以是任何類型。
注意
map[key] 不是使用 Map d的正確方式
雖然 map[key] 也有效,例如我們可以設(shè)置 map[key] = 2,這樣會(huì)將map` 視為 JavaScript 的 plain object,因此它暗含了所有相應(yīng)的限制(僅支持 string/symbol 鍵等)。
Map 還可以使用對(duì)象作為鍵
例如:
let john = { name: "John"};
//存儲(chǔ)每個(gè)用戶的來訪次數(shù)
let visitsCountMap = new Map();
//john 是 Map 中的鍵
visitsCountMap.set(john, 123};
alert(visitsCountMap.get(john)); //123
使用對(duì)象作為鍵是 Map 最值得注意和重要的功能之一。在 Object 中,我們則無法使用對(duì)象作為鍵。在 Object 中使用字符串作為鍵是可以的,但我們無法使用另一個(gè) Object 作為 Object 中的鍵。
我們來嘗試一下:
let john = { name: "John" };
let ben = { name: "Ben" };
let visitsCountObj = {}; // 嘗試使用對(duì)象
visitsCountObj[ben] = 234; // 嘗試將對(duì)象 ben 用作鍵
visitsCountObj[john] = 123; // 嘗試將對(duì)象 john 用作鍵,但我們會(huì)發(fā)現(xiàn)使用對(duì)象 ben 作為鍵存下的值會(huì)被替換掉
// 變成這樣了!
alert( visitsCountObj["[object Object]"] ); // 123
因?yàn)?visitsCountObj 是一個(gè)對(duì)象,它會(huì)將所有 Object 鍵例如上面的 john 和 ben 轉(zhuǎn)換為字符串 "[object Object]"。這顯然不是我們想要的結(jié)果。
Map 是怎么比較鍵的?
Map 使用 SameValueZero 算法來比較鍵是否相等。它和嚴(yán)格等于 === 差不多,但區(qū)別是 NaN 被看成是等于 NaN。所以 NaN 也可以被用作鍵。
這個(gè)算法不能被改變或者自定義。
鏈?zhǔn)秸{(diào)用
每一次 map.set 調(diào)用都會(huì)返回 map 本身,所以我們可以進(jìn)行“鏈?zhǔn)健闭{(diào)用:
map.set('1', 'str')
.set(1, 'num1')
.set(true, 'bool');
-----------------------------------------------------------------分割線---------------------------------------------------------
Map迭代
如果要在 map 里使用循環(huán),可以使用一下三個(gè)方法:
-
map.keys()—— 遍歷并返回所有的鍵(returns an iterable for keys), - `map.values() —— 遍歷并返回所有的值(returns an iterable for values),
-
map.entries() —— 遍歷并返回所有實(shí)體(returns an iterable for entries)[key, value],for...of` 在默認(rèn)情況下使用的就是這個(gè)。
例如:
let recipeMap = new Map([
['cucumber', 500],
['tomatoes', 350],
['onion', 50]
]);
//遍歷所有的鍵(vegetable)
for(let vagatable of recipeMap.keys()){
alert(vegetable); //cucumber, tomatoes, onion
}
//遍歷所有的值(amounts)
for(let amounts of recipeMap.values()){
alert(amounts); //500,350,50
}
//遍歷所有實(shí)體 [key, value]
for(let entry of recipeMap){ //與 recipeMap.entries() 相同
alert(entry); // cuncumber,500(and so on)
}
使用插入順序
迭代的順序與插入值的順序相同。與普通的 object 不同,Map 保留了此順序。
除此之外,Map 有內(nèi)建的 forEach 方法,與 Array 類似:
// 對(duì)每個(gè)鍵值對(duì) (key, value) 運(yùn)行 forEach 函數(shù)
recipeMap.forEach( (value, key, map) => {
alert(`${key}: ${value}`); // cucumber: 500 etc
});
Object.fromEntries: 從 Map 創(chuàng)建對(duì)象
我們剛剛以及學(xué)習(xí)了如能夠何使用 Object.entries(obj) 從普通對(duì)象(plain object)創(chuàng)建 Map。
Objcet.fromEntries 方法的作用是相反的:給定一個(gè)具有 [Key, value] 鍵值對(duì)的數(shù)組,它會(huì)根據(jù)給定數(shù)組創(chuàng)建一個(gè)對(duì)象:
let prices = Object.fromEntries([
['banana', 1],
['orange', 2],
['meat', 4]
]);
//現(xiàn)在 prices = {banana: 1, orange: 2, meat: 4)
alert(prices.orange); //2
我們可以使用 Object.fromEntries 從 Map 得到一個(gè)普通對(duì)象(plain object)。
例如,我們?cè)?Map 中存儲(chǔ)了一些數(shù)據(jù),但是我們需要把這些數(shù)據(jù)傳給需要普通對(duì)象的第三方代碼。
let map = new Map();
map.set('banana', 1);
map.set('orange', 2);
map.set('meat', 4);
let obj = Object.fromEntries(map.entries()); //創(chuàng)建一個(gè)普通對(duì)象(plain object)(*)
//完成了!
//obj = { banana: 1, orange: 2, meat: 4}
alert(obj.orange); //2
調(diào)用 map.entries() 將返回一個(gè)可迭代的鍵/值對(duì),這剛好是 Object.fromEntries 所需要的格式。
我們可以把帶 (*) 這一行寫得更短:
let obj = Object.fromEntries(map); // 省掉 .entries()
上面的代碼作用也是一樣的,因?yàn)?Object.fromEntries 期望得到一個(gè)可迭代對(duì)象作為參數(shù),而不一定是數(shù)組。并且 map 的標(biāo)準(zhǔn)迭代會(huì)返回跟map.entries() 一樣的鍵/值對(duì)。因此,我們可以獲得一個(gè)普通對(duì)象(plain object),其鍵/值對(duì)與 map 相同。
Set
set 是一個(gè)特殊的類型集合 —— “值的集合”(沒有鍵),它的每一個(gè)值只能出現(xiàn)一次。
它的主要方法如下:
-
new Set(iterble)—— 創(chuàng)建一個(gè)set,如果提供了一個(gè)iterable對(duì)象(通常是數(shù)組),將會(huì)從數(shù)組里面復(fù)制值到set中。 -
set.add(value)—— 添加一個(gè)值,返回 set 本身 -
set delete(value)—— 刪除值,如果value在這個(gè)方法調(diào)用的時(shí)候存在則返回true,否則返回false。 -
set.has(value)—— 如果value在 set 中,返回true,否則返回false。 -
set.clear()—— 清空 set -
set.size—— 返回元素個(gè)數(shù)。
它的主要特點(diǎn)是,重復(fù)使用一個(gè)值調(diào)用 set.add(value) 并不會(huì)發(fā)生上面改變。這就是 Set 里面的每一個(gè)值只出現(xiàn)一次的原因。
例如,我們有客人來訪,我們想記住他們每一個(gè)人。但是已經(jīng)來訪過的客人再次來訪,不應(yīng)造成重復(fù)記錄。每個(gè)訪客必須只被“計(jì)數(shù)”一次。
Set 可以幫助我們解決這個(gè)問題:
let set = new Set();
let john = {name: "John"};
let pete = {name: "Pete"};
let mary = {name: "Mary"};
//visits,一些訪客來訪好幾次
set.add(john);
set.add(pete);
set.add(mary);
set.add(john);
set.add(mary);
//set 只保留不重復(fù)的值
alert(set.size); //3
for(let user of set){
alert(usert.name); //John(然后Pete 和 Mary)
}
Set 的替代方法可以是一個(gè)用戶數(shù)組,用 arr.find 在每次插入值時(shí)檢查是否重復(fù)。但是這樣性能會(huì)很差,因?yàn)檫@個(gè)方法會(huì)遍歷整個(gè)數(shù)組來檢查每個(gè)元素。 Set 內(nèi)部對(duì)唯一性檢查進(jìn)行了更好的優(yōu)化。
Set 迭代(iteration)
我們可以使用 for...of 或 forEach 來遍歷 Set:
let set = new Set(["orange", "apples", "bananas"]);
for(let value of set) alert(value);
//與 forEach 相同:
set.forEach((value, valueAgain, set) => {
alert(value};
});
注意
forEach 的回調(diào)函數(shù)有三個(gè)參數(shù):一個(gè) value,然后是 同一個(gè)值 valueAgain,最后是目標(biāo)對(duì)象。沒錯(cuò),同一個(gè)值在參數(shù)里出現(xiàn)了兩次。
forEach 的回調(diào)函數(shù)有三個(gè)參數(shù),是為了與 Map 兼容。但是這對(duì)在特定情況下輕松地用 Set 代替 Map 很有幫助,反之亦然。
Map 中用于迭代的方法在 Set 中也同樣支持:
-
set.keys()—— 遍歷并返回所有的值, -
set.values()—— 與set.keys()作用相同,這是為了兼容Map, -
set.entries()—— 遍歷并返回所有的實(shí)體[value, value],它的存在也是為了兼容Map。