ES6+ Map
1. 前言
前面兩節(jié)我們學(xué)習(xí)了 Set 的相關(guān)內(nèi)容,本節(jié)我們開始學(xué)習(xí) Map 數(shù)據(jù)結(jié)構(gòu)的內(nèi)容。Map 對(duì)象和原生的 Object 類似都是存儲(chǔ)數(shù)據(jù)的,而且也推薦使用 Map 的方式來存儲(chǔ)數(shù)據(jù)。
在 ES5 中使用 Object
來存儲(chǔ)對(duì)象,然而這種存儲(chǔ)存在一些問題,比如說 Object
中的鍵是無序的,Object
的鍵只能是字符串類型等等。ES6 提供了 Map
數(shù)據(jù)結(jié)構(gòu),保存的是鍵值對(duì),而且能夠記住鍵的插入順序,并且任何值 (對(duì)象或者原始值) 都可以作為一個(gè)鍵或一個(gè)值,這樣極大地?cái)U(kuò)展了數(shù)據(jù)存儲(chǔ)的場(chǎng)景。
2. Map 使用詳情
2.1 Map 基本說明
在前面的 數(shù)據(jù)結(jié)構(gòu)擴(kuò)展 一節(jié)我們已經(jīng)了解到 Map 的基本使用。和 Set
一樣,Map
也是一個(gè)構(gòu)造函數(shù),不能直接使用,需要通過 new
的方式來創(chuàng)建一個(gè) Map
實(shí)例。
var map = Map([iterable]);
Map
對(duì)象在插入數(shù)據(jù)時(shí)是按照順序來插入的,也就是說在插入時(shí)會(huì)保持插入的位置,Object
的在插入數(shù)據(jù)時(shí)沒有順序概念。Map
對(duì)象可以被 for...of
循環(huán),在每次迭代后會(huì)返回一個(gè)形式為 [key,value]
的數(shù)組,這個(gè)我們?cè)谙旅娴睦又袝?huì)說到。
Map
的本質(zhì)其實(shí)還是一個(gè)對(duì)象,并且它也是繼承 Object 的,看下面的實(shí)例:
var map = new Map([["x", 1], ["y", 2]]);
console.log(map instanceof Object); //true
從上面的代碼中可以看出 Object 在實(shí)例 map 的原型鏈上。
在創(chuàng)建 Map
實(shí)例時(shí)可以接收一個(gè)數(shù)組或是一個(gè)可遍歷的對(duì)象作為參數(shù),這個(gè)參數(shù)內(nèi)的每一項(xiàng)是鍵值的組合 [key, value]
第一個(gè)值時(shí)鍵,第二個(gè)值時(shí)鍵的值。
在初始化 Map 對(duì)象時(shí),如果默認(rèn)參數(shù)的數(shù)組中超過兩個(gè)以上的值不會(huì)被 Map
對(duì)象讀取。
var map = new Map([["x", 1, 'a', 'b'], ["y", 2, 'c'], ["z", 3, 'd']]);
console.log(map) // Map(3) {"x" => 1, "y" => 2, "z" => 3}
上面的代碼中,從打印的結(jié)果可以看出,數(shù)組中超過的元素都會(huì)被自動(dòng)忽略。
2.2 Map 的屬性和方法
Map
提供的屬性和方法從增、刪、改、查幾個(gè)方面入手,主要有以下 5 種:
方法名 | 描述 |
---|---|
set | 接收鍵值對(duì),向 Map 實(shí)例中添加元素 |
get | 傳入指定的 key 獲取 Map 實(shí)例上的值 |
delete | 傳入指定的 key 刪除 Map 實(shí)例中對(duì)應(yīng)的值 |
clear | 清空 Map 實(shí)例 |
has | 傳入指定的 key 查找在 Map 實(shí)例中是否存在 |
size | 屬性,返回 Map 實(shí)例的長(zhǎng)度 |
Map
提供 size
屬性可以獲取 Map
實(shí)例上的長(zhǎng)度
var map = new Map([["x", 1], ["y", 2], ["z", 3]]);
console.log(map.size) // 3
set()
方法為 Map
實(shí)例添加或更新一個(gè)指定了鍵(key)和值(value)的鍵值對(duì)。
myMap.set(key, value);
通常情況下不會(huì)一開始就初始化值,而是動(dòng)態(tài)地添加,或更新 Map
時(shí)需要用到 set
方法,可以新增和修改 Map
實(shí)例的值。而且 key 值可以是任意類型的,查看如下示例:
var map = new Map();
var str = 'string';
var obj = {};
var arr = [];
var fun = function() {};
map.set(str, '鍵的類型字符串');
map.set(obj, '鍵的類型對(duì)象');
map.set(arr, '鍵的類型數(shù)組');
map.set(fun, '鍵的類型函數(shù)');
上面的代碼中,我們定義了不同類型的變量,使用這些變量為 map
添加數(shù)據(jù)。相比 Object
對(duì)象,擴(kuò)展性更強(qiáng)了。另外還可以鏈?zhǔn)教砑渔I值對(duì):
var map = new Map();
map.set('a', 1).set('b', 2).set('c', 3);
console.log(map); // Map(3) {"a" => 1, "b" => 2, "c" => 3}
使用鏈?zhǔn)教砑渔I值對(duì)的方式比較簡(jiǎn)潔,如果需要添加多個(gè)值,建議使用這樣的方式去添加。
get()
方法是接收一個(gè)指定的鍵(key)返回一個(gè) Map
對(duì)象中與這個(gè)指定鍵相關(guān)聯(lián)的值,如果找不到這個(gè)鍵則返回 undefined
。
myMap.get(key);
使用上面的示例,可以通過 get
方法獲取對(duì)應(yīng)的值:
console.log(map.get('string')); // "鍵的類型字符串"
console.log(map.get(str)); // "鍵的類型字符串"
console.log(map.get(obj)); // "鍵的類型對(duì)象"
console.log(map.get(arr)); // "鍵的類型數(shù)組"
console.log(map.get(fun)); // "鍵的類型數(shù)組"
上面的代碼可以看出,我們可以直接使用鍵的值去獲取 Map 實(shí)例上對(duì)應(yīng)的值,也可以通過定義變量的方式去獲取。
has()
方法是用于判斷指定的鍵是否存在,并返回一個(gè) bool 值,如果指定元素存在于 Map
中,則返回 true
,否則返回 false
。
myMap.has(key);
實(shí)例:
var map = new Map();
map.set("a", 11);
map.has("a"); // true
map.has("b"); // false
delete()
方法用于移除 Map
實(shí)例上的指定元素,如果 Map
對(duì)象中存在該元素,則移除它并返回 true
;否則如果該元素不存在則返回 false
。
myMap.delete(key);
實(shí)例:
var map = new Map();
map.set("a", 11);
map.delete("a"); // true
map.has("a"); // false
clear()
方法會(huì)移除 Map 對(duì)象中的所有元素,返回 undefined
。
myMap.clear(key);
實(shí)例:
var map = new Map();
map.set("a", 11);
map.clear(); // 返回 undefined
這里需要注意的是 clear()
返回的值是 undefined
而不是 true
所以如果在判斷結(jié)果的時(shí)候需要注意這一點(diǎn)。
2.3 Map 的擴(kuò)展方法
和 Set
數(shù)據(jù)結(jié)構(gòu)一樣,Map 也提供了三個(gè)獲取 Map 對(duì)象的鍵值以及鍵值對(duì)組合的方法:
方法名 | 描述 |
---|---|
values | 返回 Map 實(shí)例中的值作為一個(gè)可以遍歷的對(duì)象 |
keys | 返回 Map 實(shí)例中的鍵作為一個(gè)可以遍歷的對(duì)象 |
entries | 返回 Map 實(shí)例中的鍵值對(duì)作為一個(gè)可以遍歷的對(duì)象 |
keys()
方法是獲取 Map
實(shí)例上的鍵,并返回一個(gè)可迭代(Iterator)的對(duì)象。
myMap.keys();
var map = new Map();
map.set('a', 1);
map.set('b', 2);
map.set('c', 3);
var keys = map.keys()
console.log(keys.next().value); // "a"
console.log(keys.next().value); // "b"
console.log(keys.next().value); // "c"
獲取后的 keys
結(jié)構(gòu)可以被迭代器上的 next
函數(shù)獲取到對(duì)應(yīng)值。
values()
方法是獲取 Map
實(shí)例上元素的值,并返回一個(gè)可迭代(Iterator)的對(duì)象。
myMap.values();
實(shí)例:
var map = new Map();
map.set('a', 1);
map.set('b', 2);
map.set('c', 3);
var values = map.values()
console.log(values.next().value); // 1
console.log(values.next().value); // 2
console.log(values.next().value); // 3
獲取后的 values
結(jié)構(gòu)可以被迭代器上的 next
函數(shù)獲取到對(duì)應(yīng)值。
entries()
方法返回一個(gè)包含 [key, value]
的可迭代(Iterator)的對(duì)象,返回的迭代器的迭代順序與 Map
實(shí)例的插入順序相同。
myMap.entries()
實(shí)例:
var map = new Map();
map.set('a', 1);
map.set('b', 2);
map.set('c', 3);
var values = map.values()
console.log(values.next().value); // 1
console.log(values.next().value); // 2
console.log(values.next().value); // 3
keys()
、values()
、entries()
都可以被 for...of
循環(huán)。
var map = new Map([["x", 1], ["y", 2], ["z", 3]]);
for (let value of map.values()) {
console.log(value);
}
// 1
// 2
// 3
for (let [key, value] of map.entries()) {
console.log(key + " = " + value);
}
// x = 1
// y = 2
// z = 3
注意在循環(huán) entries()
結(jié)果的時(shí)候,因?yàn)槊恳豁?xiàng)是包含鍵值的數(shù)組,可以通過 [key, value]
這種數(shù)組結(jié)構(gòu)的方式把鍵值結(jié)構(gòu)出來直接使用。
3. Map 和 Object
Map
和 Object
有非常多的相似的地方,Map
的出現(xiàn)也是為了彌補(bǔ) Object
的不足。 Object
的鍵只能是字符串,Map
的鍵可以是任意類型的值(包括對(duì)象),所以 Map
是一種更完善的 Hash 結(jié)構(gòu)實(shí)現(xiàn)。
3.1 鍵無序問題
Object 的鍵是無序的,當(dāng)鍵可以隱式轉(zhuǎn)換為數(shù)值時(shí),在循環(huán)的時(shí)候就會(huì)被優(yōu)先排序。這也是為什么要求最好不要使用 Number 類型作為對(duì)象的屬性。
var obj = {
c: 'C',
3: 3,
a: 'A',
1: 1,
}
for (let key in obj) {
console.log(key, obj[key])
}
// 1 1
// 3 3
// c C
// a A
Map
會(huì)記錄插入的順序,存放的是鍵值對(duì)的組合,并且不會(huì)做類型轉(zhuǎn)換。Map
可以用 forEach 循環(huán)。
var map = new Map();
map.set('c', 'C').set(3, 3).set('a', 'A').set(1, 1);
map.forEach((item, key) => {
console.log(key, item, typeof key)
})
// c C string
// 3 3 "number"
// a A string
// 1 1 "number"
從上面的代碼中,使用 typeof 去檢查 key 的數(shù)據(jù)類型,可以看出 Map
并不會(huì)對(duì)鍵做類型轉(zhuǎn)換。
3.2 應(yīng)用場(chǎng)景
Object 不僅是存儲(chǔ)數(shù)據(jù)用的,它還可以有自己的內(nèi)部邏輯。屬性的值是一個(gè)函數(shù)時(shí),是可以被執(zhí)行的,并且可以通過 this 拿到對(duì)象上的屬性。
var obj = {
id: 1,
desc: "imooc ES6 wiki",
print: function(){
console.log(this.desc)
}
}
console.log(obj.print()); //"imooc ES6 wiki"
所以,盡管 Map 相對(duì)于 Object 有很多優(yōu)點(diǎn),但 Object 在某些場(chǎng)景更易于使用,比如上面的實(shí)例。畢竟 Object 是 JavaScript 中最基礎(chǔ)的概念,給出使用場(chǎng)景的幾個(gè)參考。
使用 Object 的場(chǎng)景:
- 如果你知道所有的 key,它們都為字符串或整數(shù)(或是 Symbol 類型),而你只需要一個(gè)簡(jiǎn)單的結(jié)構(gòu)去存儲(chǔ)這些數(shù)據(jù),Object 無疑是一個(gè)非常好的選擇。另外,獲取對(duì)象的值時(shí) Object 的性能要優(yōu)于 Map;
- 如果需要在對(duì)象中保持自己獨(dú)有的邏輯和屬性,比如上面實(shí)例,只能使用 Object;
- JSON 可以直接轉(zhuǎn)為 Object,但 Map 不行。因此,在某些我們必須使用 JSON 的情況下,應(yīng)將 Object 視為首選。
使用 Map 的場(chǎng)景:
- Map 是一個(gè)純哈希結(jié)構(gòu),使用
delete
對(duì) Object 的屬性進(jìn)行刪除操作時(shí)存在很多性能上的問題。所以,在有大量數(shù)據(jù),或是多數(shù)據(jù)進(jìn)行增、刪操作的場(chǎng)景中,使用 Map 更合適; - Map 會(huì)保留所有元素的插入順序。在數(shù)據(jù)迭代時(shí)不會(huì)有亂序的情況。所以,如果考慮到元素迭代或順序,使用 Map 更好;
- Map 的鍵值可以是任意類型,而且不會(huì)做類型轉(zhuǎn)換。所以,在鍵值不確定的情況下,保證鍵值不被隱式轉(zhuǎn)換的情況下可以優(yōu)選 Map。
4. 判斷鍵值相等的問題
因?yàn)樵?Map 對(duì)象中鍵可以是任意值,所以對(duì)鍵的說明有以下幾點(diǎn):
NaN
是與NaN
相等的(雖然NaN !== NaN
),剩下所有其它的值是根據(jù)===
運(yùn)算符的結(jié)果判斷是否相等;- 在目前的 ECMAScript 規(guī)范中,
-0
和+0
被認(rèn)為是相等的,盡管這在早期的草案中并不是這樣。
5. 小結(jié)
本節(jié)我們深入地學(xué)習(xí)了 Map 的使用情況,并對(duì)比 Object 給出了幾個(gè)使用場(chǎng)景的參考。學(xué)習(xí)完本章你需要知道以下幾點(diǎn):
- 鍵的類型可以是任意的,可以使用
function
、對(duì)象等等作為 key; - 鍵是有順序,根據(jù)添加的順序決定的;
- Map 是一個(gè)完善的 Hash 結(jié)構(gòu),在存放大數(shù)據(jù),或在頻繁增、刪鍵值時(shí)表現(xiàn)優(yōu)異。