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