JSMap and Set(映射和集合)

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 鍵例如上面的 johnben 轉(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.fromEntriesMap 得到一個(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...offorEach 來遍歷 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。
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容