Map結構的目的和基本用法
JavaScript的對象(Object)本質上是鍵值對的集合(Hash結構),但是只能用字符串作為鍵。這給它的使用帶來了很大的限制。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
</head>
<body>
<div id="myDiv"></div>
<script type="text/javascript">
var data = {};
var element = document.getElementById("myDiv");
data[element]=element;
data["[Object HTMLDivElement]"];
console.log(data);
</script>
</body>
</html>
|
上面的代碼原意是將一個DOM節點作為對象data的鍵,但是由於對象只接受字符串作為鍵名,所以element被自動轉化為字符串[Object HTMLdivElement]。
為解決這個問題,ES6提供了Map數據結構。它類似於對象,也是鍵值對的集合,但是“鍵”的范圍不限於字符串,各種類型的值(包括對象)都可以當作鍵。也就是說,Object結構提供了“字符串-值”的對應,Map結構提供了“值-值”的對應,是一種更完善的Hash結構實現。如果你需要“鍵值對”的數據結構,Map比Object更適合。
1
2
3
4
5
6
7
|
var m = new Map();
var o = {p:"Hello World"};
m.set(o,"content");
m.get(o);//"content"
m.has(o);//true
m.delete(o);//true
m.has(o);//false
|
上面的代碼使用set方法將對象o當作m的一個鍵,然后又實用get方法讀取這個鍵,最后使用delet方法刪除了這個鍵。
作為構造函數,Map也可以接受一個數組作為參數。該數組的成員是一個個表示鍵值對的數組。
1
2
3
4
5
6
|
var map = new Map([["name","張三"],["title","Author"]]);
console.log(map.size);//2
console.log(map.has("name"));//true
console.log(map.get("name"));//張三
console.log(map.has("title"));//true
console.log(map.get("title"));//"Author"
|
上面的代碼在新建Map實例時就制定了兩個鍵-name和title。
Map構造函數接受數組作為參數,實際上執行的是下面的算法。
1
2
3
4
5
6
7
8
9
|
var items= [
["name","張三"],
["title","Author"]
];
var map = new Map();
items.forEach(function(key,value){
map.set(key,value);
});
console.log(items);
|
如果對同一個鍵多次賦值,后面的值將覆蓋前面的值。
1
2
3
|
let map = new Map();
map.set(1,'aaa').set(1,'bbb');
map.get(1);//"bbb"
|
上面的代碼對鍵1連續賦值兩次,后一次的值覆蓋了前一次的值。
如果讀取一個未知的鍵,則返回undefined。
1
|
console.log(new Map().get('adsdfdsf'));
|
注意,只有對同一個對象的應用,Map結構才將其視為同一個鍵。這一點要非常小心。
1
2
3
|
var map =new Map();
map.set(['a'],555);
console.log(map.get(['a']));//undefined
|
上面的set和get 方法,表面上是針對同一個鍵,但實際上是兩個值,內存地址是不一樣的,因此get方法無法讀取該鍵,返回undefined。
同理,同樣的值的兩個實例,在Map結構中被視為兩個鍵。
1
2
3
4
5
6
|
var map = new Map();
var k1=['a'];
var k2=['a'];
map.set(k1,111).set(k2,222);
console.log(map.get(k1));//111
console.log(map.get(k2));//222
|
上面的代碼中,變量k1和k2的值是一樣的,但他們在Map結構中被視為兩個鍵。
由上可知,Map的鍵實際上是跟內存地址綁定的,只要內存地址不一樣,就視為兩個鍵。這就解決了同名屬性碰撞(clash)的問題,我們擴展別人的庫時,如果使用對象作為鍵名,不用擔心自己的屬性與原作者的屬性同名。
如果Map的鍵是一個簡單的值(數字、字符串、布爾值),則只要兩個值嚴格相等,Map就將其視為一個鍵,包括0和-0.另外,雖然NaN不嚴格對於自身,但Map將其視為同一個鍵。
1
2
3
4
5
|
var map =new Map();
map.set(NaN,123);
map.get(NaN);//123
map.set(-0,123);
map.get(+0);//123
|
實例的屬性和操作方法
Map結構的實例有以下屬性和操作方法。
size屬性:size屬性返回Map結構的成員總數。
1
2
3
4
|
var map =new Map();
map.set('foo',true);
map.set('bar',false);
console.log(map.size);
|
set(key,value)方法
set方法設置key所對應的鍵值,然后返回整個Map結構。如果可以已經有值,則鍵值會被更新,否則就新生成該鍵。
1
2
3
4
|
var m = new Map();
m.set("edition",6);//鍵值是字符串
m.set(262,"standard");//鍵是數值
m.set(undefined,"nah");//鍵是undefined
|
set 方法返回的是Map本身,因此可以采用鏈式寫法。
1
|
let map = new Map().set(1,'a').set(2,'b').set(3,'c');
|
get( key ):get方法讀取key對應的鍵值,如果找不到key,則返回undefined。
has( key ):has方法返回一個布爾值,表示某個鍵是否在Map數據結構中。
delete( key ):delete 方法刪除某個鍵,返回true。如果刪除失敗,返回false。
clear( ):clear方法清除所有成員,沒有返回值。
遍歷方法:
Map原生提供3個遍歷器生成函數和1個遍歷方法。
- keys( ):返回鍵名的遍歷器。
- values( ):返回鍵值的遍歷器。
- entries( ):返回所有成員的遍歷器。
- forEach( ):遍歷Map的所有成員。
下面是使用實例。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
let map = new Map([['F','no'],['T','yes']]);
for (let key of map.keys()){
console.log(key);
}
for(let value of map.values()){
console.log(value);
}
for(let item of map.entries()){
console.log(item[0],item[1]);
}
for(let[key,value] of map.entries()){
console.log(key,value);
}
for(let [key,value] of map){
console.log(key,value);
}
|
以上最后的那個例子,表示Map結構的默認遍歷器接口(Symbol.iterator屬性)就是entries方法。
Map結構轉為數組結構比較快速的方法是結合使用擴展運算符(…)。
1
2
3
4
5
|
let map = new Map([[1,'one'],[2,'two'],[3,'three']]);
console.log([...map.keys()]);
console.log([...map.values()]);
console.log([...map.entries()]);
console.log([...map]);
|
結合數組的map方法,filter方法,可以實現Map的遍歷和過濾(Map本身沒有map 和filter方法)。
1
2
3
4
5
|
let map0 = new Map().set(1,'a').set(2,'b').set(3,'c');
let map1 = new Map([...map0].filter(([k,v])=>k<3));
let map2 = new Map([...map0].map(([k,v])=>[k*2,'_'+v]));
console.log(map1);
console.log(map2);
|
此外,Map還有一個forEach方法,與數組的forEach方法類似,也可以實現遍歷。
1
2
3
|
map0.forEach(function(value,key,map0){
console.log("Key: %s, Value: %s",key,value);
});
|
與其他數據結構的互相轉換
Map轉換為數組
前面已經提過,Map轉為數組最方便的方法就是使用擴展運算符(…)。
1
2
|
let myMap =new Map().set(true,7).set({foo:3},['abc']);
console.log([...myMap]);
|
數組轉為Map
將數組傳入Map構造函數就可以轉為Map。
1
|
new Map([[true,7],[{foo:3},['abc']]]);
|
Map轉為對象
如果Map的所有鍵都是字符串,則其可以轉為對象。
1
2
3
4
5
6
7
8
9
|
function strMapToObj(strMap){
let obj =Object.create(null);
for(let[k,v] of strMap){
obj[k] =v;
}
return obj;
}
let myMap =new Map().set('yes',true).set('no',false);
console.log(strMapToObj(myMap));
|
對象轉為Map
1
2
3
4
5
6
7
8
9
|
function objToStrMap(obj){
let strMap =new Map();
for(let k of Object.keys(obj)){
strMap.set(k,obj[k]);
}
return strMap;
}
console.log(objToStrMap({yes:true,no:false}));
|