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