W3Cschool
恭喜您成為首批注冊(cè)用戶
獲得88經(jīng)驗(yàn)值獎(jiǎng)勵(lì)
學(xué)到現(xiàn)在,我們已經(jīng)了解了以下復(fù)雜的數(shù)據(jù)結(jié)構(gòu):
但這還不足以應(yīng)對(duì)現(xiàn)實(shí)情況。這就是為什么存在 ?Map
? 和 ?Set
?。
Map 是一個(gè)帶鍵的數(shù)據(jù)項(xiàng)的集合,就像一個(gè) ?Object
? 一樣。 但是它們最大的差別是 ?Map
? 允許任何類(lèi)型的鍵(key)。
它的方法和屬性如下:
它的方法和屬性如下:
new Map()
? —— 創(chuàng)建 map。map.set(key, value)
? —— 根據(jù)鍵存儲(chǔ)值。map.get(key)
? —— 根據(jù)鍵來(lái)返回值,如果 ?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, 'bool1'); // 布爾值鍵
// 還記得普通的 Object 嗎? 它會(huì)將鍵轉(zhuǎn)化為字符串
// Map 則會(huì)保留鍵的類(lèi)型,所以下面這兩個(gè)結(jié)果不同:
alert( map.get(1) ); // 'num1'
alert( map.get('1') ); // 'str1'
alert( map.size ); // 3
如我們所見(jiàn),與對(duì)象不同,鍵不會(huì)被轉(zhuǎn)換成字符串。鍵可以是任何類(lèi)型。
?
map[key]
? 不是使用 ?Map
? 的正確方式雖然
map[key]
也有效,例如我們可以設(shè)置map[key] = 2
,這樣會(huì)將map
視為 JavaScript 的 plain object,因此它暗含了所有相應(yīng)的限制(僅支持 string/symbol 鍵等)。
所以我們應(yīng)該使用
map
方法:set
和get
等。
Map 還可以使用對(duì)象作為鍵。
例如:
let john = { name: "John" };
// 存儲(chǔ)每個(gè)用戶的來(lái)訪次數(shù)
let visitsCountMap = new Map();
// john 是 Map 中的鍵
visitsCountMap.set(john, 123);
alert( visitsCountMap.get(john) ); // 123
使用對(duì)象作為鍵是 Map
最值得注意和重要的功能之一。在 Object
中,我們則無(wú)法使用對(duì)象作為鍵。在 Object
中使用字符串作為鍵是可以的,但我們無(wú)法使用另一個(gè) Object
作為 Object
中的鍵。
我們來(lái)嘗試一下:
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)?nbsp;visitsCountObj
是一個(gè)對(duì)象,它會(huì)將所有 Object
鍵例如上面的 john
和 ben
轉(zhuǎn)換為字符串 "[object Object]"
。這顯然不是我們想要的結(jié)果。
?
Map
? 是怎么比較鍵的?
Map
使用 SameValueZero 算法來(lái)比較鍵是否相等。它和嚴(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', 'str1') .set(1, 'num1') .set(true, 'bool1');
如果要在 ?map
? 里使用循環(huán),可以使用以下三個(gè)方法:
map.keys()
? —— 遍歷并返回一個(gè)包含所有鍵的可迭代對(duì)象,map.values()
? —— 遍歷并返回一個(gè)包含所有值的可迭代對(duì)象,map.entries()
? —— 遍歷并返回一個(gè)包含所有實(shí)體 ?[key, value]
? 的可迭代對(duì)象,?for..of
? 在默認(rèn)情況下使用的就是這個(gè)。例如:
let recipeMap = new Map([
['cucumber', 500],
['tomatoes', 350],
['onion', 50]
]);
// 遍歷所有的鍵(vegetables)
for (let vegetable of recipeMap.keys()) {
alert(vegetable); // cucumber, tomatoes, onion
}
// 遍歷所有的值(amounts)
for (let amount of recipeMap.values()) {
alert(amount); // 500, 350, 50
}
// 遍歷所有的實(shí)體 [key, value]
for (let entry of recipeMap) { // 與 recipeMap.entries() 相同
alert(entry); // cucumber,500 (and so on)
}
使用插入順序
迭代的順序與插入值的順序相同。與普通的
Object
不同,Map
保留了此順序。
除此之外,Map
有內(nèi)建的 forEach
方法,與 Array
類(lèi)似:
// 對(duì)每個(gè)鍵值對(duì) (key, value) 運(yùn)行 forEach 函數(shù)
recipeMap.forEach( (value, key, map) => {
alert(`${key}: ${value}`); // cucumber: 500 etc
});
當(dāng)創(chuàng)建一個(gè) Map
后,我們可以傳入一個(gè)帶有鍵值對(duì)的數(shù)組(或其它可迭代對(duì)象)來(lái)進(jìn)行初始化,如下所示:
// 鍵值對(duì) [key, value] 數(shù)組
let map = new Map([
['1', 'str1'],
[1, 'num1'],
[true, 'bool1']
]);
alert( map.get('1') ); // str1
如果我們想從一個(gè)已有的普通對(duì)象(plain object)來(lái)創(chuàng)建一個(gè) Map
,那么我們可以使用內(nèi)建方法 Object.entries(obj),該方法返回對(duì)象的鍵/值對(duì)數(shù)組,該數(shù)組格式完全按照 Map
所需的格式。
所以可以像下面這樣從一個(gè)對(duì)象創(chuàng)建一個(gè) Map:
let obj = {
name: "John",
age: 30
};
let map = new Map(Object.entries(obj));
alert( map.get('name') ); // John
這里,Object.entries
返回鍵/值對(duì)數(shù)組:[ ["name","John"], ["age", 30] ]
。這就是 Map
所需要的格式。
我們剛剛已經(jīng)學(xué)習(xí)了如何使用 Object.entries(obj)
從普通對(duì)象(plain object)創(chuàng)建 Map
。
Object.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è)?nbsp;Map
中存儲(chǔ)了一些數(shù)據(jù),但是我們需要把這些數(shù)據(jù)傳給需要普通對(duì)象(plain object)的第三方代碼。
我們來(lái)開(kāi)始:
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
所需要的格式。
我們可以把帶 (*)
這一行寫(xiě)得更短:
let obj = Object.fromEntries(map); // 省掉 .entries()
上面的代碼作用也是一樣的,因?yàn)?nbsp;Object.fromEntries
期望得到一個(gè)可迭代對(duì)象作為參數(shù),而不一定是數(shù)組。并且 map
的標(biāo)準(zhǔn)迭代會(huì)返回跟 map.entries()
一樣的鍵/值對(duì)。因此,我們可以獲得一個(gè)普通對(duì)象(plain object),其鍵/值對(duì)與 map
相同。
Set
是一個(gè)特殊的類(lèi)型集合 —— “值的集合”(沒(méi)有鍵),它的每一個(gè)值只能出現(xiàn)一次。
它的主要方法如下:
new Set(iterable)
? —— 創(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)一次的原因。
例如,我們有客人來(lái)訪,我們想記住他們每一個(gè)人。但是已經(jīng)來(lái)訪過(guò)的客人再次來(lái)訪,不應(yīng)造成重復(fù)記錄。每個(gè)訪客必須只被“計(jì)數(shù)”一次。
Set
可以幫助我們解決這個(gè)問(wèn)題:
let set = new Set();
let john = { name: "John" };
let pete = { name: "Pete" };
let mary = { name: "Mary" };
// visits,一些訪客來(lái)訪好幾次
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(user.name); // John(然后 Pete 和 Mary)
}
Set
的替代方法可以是一個(gè)用戶數(shù)組,用 arr.find 在每次插入值時(shí)檢查是否重復(fù)。但是這樣性能會(huì)很差,因?yàn)檫@個(gè)方法會(huì)遍歷整個(gè)數(shù)組來(lái)檢查每個(gè)元素。Set
內(nèi)部對(duì)唯一性檢查進(jìn)行了更好的優(yōu)化。
我們可以使用 for..of
或 forEach
來(lái)遍歷 Set:
let set = new Set(["oranges", "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ì)象。沒(méi)錯(cuò),同一個(gè)值在參數(shù)里出現(xiàn)了兩次。
forEach
的回調(diào)函數(shù)有三個(gè)參數(shù),是為了與 Map
兼容。當(dāng)然,這看起來(lái)確實(shí)有些奇怪。但是這對(duì)在特定情況下輕松地用 Set
代替 Map
很有幫助,反之亦然。
Map
中用于迭代的方法在 Set
中也同樣支持:
set.keys()
? —— 遍歷并返回一個(gè)包含所有值的可迭代對(duì)象,set.values()
? —— 與 ?set.keys()
? 作用相同,這是為了兼容 ?Map
?,set.entries()
? —— 遍歷并返回一個(gè)包含所有的實(shí)體 ?[value, value]
? 的可迭代對(duì)象,它的存在也是為了兼容 ?Map
?。Map
—— 是一個(gè)帶鍵的數(shù)據(jù)項(xiàng)的集合。
方法和屬性如下:
new Map([iterable])
? —— 創(chuàng)建 map,可選擇帶有 ?[key,value]
? 對(duì)的 ?iterable
?(例如數(shù)組)來(lái)進(jìn)行初始化。map.set(key, value)
? —— 根據(jù)鍵存儲(chǔ)值,返回 map 自身。map.get(key)
? —— 根據(jù)鍵來(lái)返回值,如果 ?map
? 中不存在對(duì)應(yīng)的 ?key
?,則返回 ?undefined
?。map.has(key)
? —— 如果 ?key
? 存在則返回 ?true
?,否則返回 ?false
?。map.delete(key)
? —— 刪除指定鍵對(duì)應(yīng)的值,如果在調(diào)用時(shí) ?key
? 存在,則返回 ?true
?,否則返回 ?false
?。map.clear()
? —— 清空 map 。map.size
? —— 返回當(dāng)前元素個(gè)數(shù)。與普通對(duì)象 Object
的不同點(diǎn):
size
? 屬性。Set
—— 是一組唯一值的集合。
方法和屬性:
new Set([iterable])
? —— 創(chuàng)建 set,可選擇帶有 ?iterable
?(例如數(shù)組)來(lái)進(jìn)行初始化。set.add(value)
? —— 添加一個(gè)值(如果 ?value
? 存在則不做任何修改),返回 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ù)。在 Map
和 Set
中迭代總是按照值插入的順序進(jìn)行的,所以我們不能說(shuō)這些集合是無(wú)序的,但是我們不能對(duì)元素進(jìn)行重新排序,也不能直接按其編號(hào)來(lái)獲取元素。
重要程度: 5
定義 ?arr
? 為一個(gè)數(shù)組。
創(chuàng)建一個(gè)函數(shù) ?unique(arr)
?,該函數(shù)返回一個(gè)由 ?arr
? 中所有唯一元素所組成的數(shù)組。
例如:
function unique(arr) {
/* 你的代碼 */
}
let values = ["Hare", "Krishna", "Hare", "Krishna",
"Krishna", "Krishna", "Hare", "Hare", ":-O"
];
alert( unique(values) ); // Hare, Krishna, :-O
P.S. 這里用到了 string 類(lèi)型,但其實(shí)可以是任何類(lèi)型的值。
P.S. 使用 ?Set
? 來(lái)存儲(chǔ)唯一值。
function unique(arr) {
return Array.from(new Set(arr));
}
重要程度: 4
Anagrams 是具有相同數(shù)量相同字母但是順序不同的單詞。
例如:
nap - pan
ear - are - era
cheaters - hectares - teachers
寫(xiě)一個(gè)函數(shù) aclean(arr)
,它返回被清除了字謎(anagrams)的數(shù)組。
例如:
let arr = ["nap", "teachers", "cheaters", "PAN", "ear", "era", "hectares"];
alert( aclean(arr) ); // "nap,teachers,ear" or "PAN,cheaters,era"
對(duì)于所有的字謎(anagram)組,都應(yīng)該保留其中一個(gè)詞,但保留的具體是哪一個(gè)并不重要。
為了找到所有字謎(anagram),讓我們把每個(gè)單詞打散為字母并進(jìn)行排序。當(dāng)字母被排序后,所有的字謎就都一樣了。
例如:
nap, pan -> anp
ear, era, are -> aer
cheaters, hectares, teachers -> aceehrst
...
我們將使用進(jìn)行字母排序后的單詞的變體(variant)作為 map 的鍵,每個(gè)鍵僅對(duì)應(yīng)存儲(chǔ)一個(gè)值:
function aclean(arr) {
let map = new Map();
for (let word of arr) {
// 將單詞 split 成字母,對(duì)字母進(jìn)行排序,之后再 join 回來(lái)
let sorted = word.toLowerCase().split('').sort().join(''); // (*)
map.set(sorted, word);
}
return Array.from(map.values());
}
let arr = ["nap", "teachers", "cheaters", "PAN", "ear", "era", "hectares"];
alert( aclean(arr) );
字母排序在 (*)
行以鏈?zhǔn)秸{(diào)用的方式完成。
為了方便,我們把它分解為多行:
let sorted = word // PAN
.toLowerCase() // pan
.split('') // ['p','a','n']
.sort() // ['a','n','p']
.join(''); // anp
兩個(gè)不同的單詞 'PAN'
和 'nap'
得到了同樣的字母排序形式 'anp'
。
下一行是將單詞放入 map:
map.set(sorted, word);
如果我們?cè)俅斡龅较嗤帜概判蛐问降膯卧~,那么它將會(huì)覆蓋 map 中有相同鍵的前一個(gè)值。因此,每個(gè)字母形式(譯注:排序后的)最多只有一個(gè)單詞。(譯注:并且是每個(gè)字母形式中最靠后的那個(gè)值)
最后,Array.from(map.values())
將 map 的值迭代(我們不需要結(jié)果的鍵)為數(shù)組形式,并返回這個(gè)數(shù)組。
在這里,我們也可以使用普通對(duì)象(plain object)而不用 Map
,因?yàn)殒I就是字符串。
下面是解決方案:
function aclean(arr) {
let obj = {};
for (let i = 0; i < arr.length; i++) {
let sorted = arr[i].toLowerCase().split("").sort().join("");
obj[sorted] = arr[i];
}
return Object.values(obj);
}
let arr = ["nap", "teachers", "cheaters", "PAN", "ear", "era", "hectares"];
alert( aclean(arr) );
重要程度: 5
我們期望使用 ?map.keys()
? 得到一個(gè)數(shù)組,然后使用例如 ?.push
? 等特定的方法對(duì)其進(jìn)行處理。
但是運(yùn)行不了:
let map = new Map();
map.set("name", "John");
let keys = map.keys();
// Error: keys.push is not a function
keys.push("more");
為什么?我們應(yīng)該如何修改代碼讓 keys.push
工作?
這是因?yàn)?nbsp;map.keys()
返回的是可迭代對(duì)象而非數(shù)組。
我們可以使用方法 Array.from
來(lái)將它轉(zhuǎn)換為數(shù)組:
let map = new Map();
map.set("name", "John");
let keys = Array.from(map.keys());
keys.push("more");
alert(keys); // name, more
Copyright©2021 w3cschool編程獅|閩ICP備15016281號(hào)-3|閩公網(wǎng)安備35020302033924號(hào)
違法和不良信息舉報(bào)電話:173-0602-2364|舉報(bào)郵箱:jubao@eeedong.com
掃描二維碼
下載編程獅App
編程獅公眾號(hào)
聯(lián)系方式:
更多建議: