详解map?创建map集合?Map集合支持的方法?传入数组来初始化Map集合?同名属性碰撞?遍历?转为数组?forEach?
JS的对象(Object),本质上是键值对的集合(Hash结构),但是传统上只能用字符串当做键,这给它的使用带来了很大的限制。
为了解决这个问题,ES6提供了MAP数据结构。它类似于对象,也是键值对的集合,但是“键”的范围不限于字符串,各种类型的值(包括对象)都可以当作键。也就是说,Object结构提供了“字符串-值”的对应,Map结构提供了“值-值”的对应,是一种更完善的Hash结构实现。
ES6中的Map类型是一种储存着许多键值对的有序列表,其中的键名和对应的值支持所有的数据类型。键名的等价行判断是通过调用Object.is()方法实现的,所以数字5与字符串“5”会被判定为两种类型,可以分别作为独立的两个键出现在程序中,这一点与对象不一样,因为对象的属性名总会被强制转换成字符串类型。
注意:有一个例外,Map集合中将+0和-0视为相等,与Object.is()结果不同
如果需要“键值对”的数据结构,Map比Object更合适
1、创建Map集合
如果要向Map集合中添加新的元素,可以调用set()方法并分别传入键名和对应值作为两个参数;如果要从集合中获取信息,可以调用get()方法。
let map = new Map();
map.set("title", "Understanding ES6");
map.set("year", 2018);
console.log(map.get("title")); // "Understanding ES6"
console.log(map.get("year")); // 2018
在这个示例中,两组键值对分别被存入了集合Map中,键名“title”对应的值是一个字符串,键名“year”对应的值是一个数字。
调用get()方法可以获得两个键名对应的值。如果调用get()方法时传入键名在Map集合中不存在,则会返回undefined。
在对象中,无法用对象作为对象属性的键名。但是在Map集合中,却可以这样做
let map = new Map(),
key1 = {},
key2 = {};
map.set(key1, 5);
map.set(key2, 42);
console.log(map.get(key1)); // 5
console.log(map.get(key2)); // 42
在这段代码中,分别用对象key1和key2作为两个键名在Map集合里存储了不同的值。这些键名不会被强制转换成其他形式,所以这两个对象在集合中是独立存在的,也就是独立存在的,也就是独立存在的,也就是说,以后不需要修改对象本身就可以为其添加一些附加信息。
2、Map集合支持的方法
在设计语言新标准时,委员会为Map集合与Set集合设计了如下3个通用的方法
(1)has(key)检测指定的键名在Map集合中是否已经存在。
(2)delete(key)从Map集合中移除指定键名及其对应的值。
(3)clear()移除Map集合中的所有键值对。
Map集合同样支持size属性,其代表当前集合中包含的键值对数量。
let map = new Map();
map.set("name", "huochai");
map.set("age", 25);
console.log(map.size); // 2
console.log(map.has("name")); // true
console.log(map.get("name")); // "huochai"
console.log(map.has("age")); // true
console.log(map.get("age")); // 25
map.delete("name");
console.log(map.has("name")); // false
console.log(map.get("name")); // undefined
console.log(map.size); // 1
map.clear();
console.log(map.has("name")); // false
console.log(map.get("name")); // undefined
console.log(map.has("age")); // false
console.log(map.get("age")); // undefined
console.log(map.size); // 0
Map集合的size属性与Set集合中的size属性类似,其值为集合中键值对的数量。在此示例中,首先为Map的实例添加“name”和“age”这两个键名;然后调用has()方法,分别传入两个键名,返回结果为true;调用delete()方法移除“name”,再用has()方法检测返回false,且size属性值减少1;最后调用clear()方法移除剩余的键值对,调用has()方法检测全部返回false,size属性的值变为0;clear()方法可以快速清除Map集合中的数据,同样,Map集合也支持批量添加数据。
3、传入数据来初始化Map集合
可以向构造函数传入数组来初始化一个Map集合,这一点同样与Set集合相似。数组中的每个元素都是一个子数组,子数组中包含一个键值对的键名与值两个元素。因此,整个Map集合中包含的全是这样的两个元素数组
let map = new Map([["name", "huochai"], []"age", 25]);
console.log(map.has("name")); // true
console.log(map.get("name")); // "huochai"
console.log(map.has("age")); // true
console.log(map.get("age")); // 25
console.log(map.size); // 2
初始化构造函数之后,键名“name”和“age”分别被添加到Map集合中。数组包裹数组的模式看起来可能有点奇怪,但由于Map集合可以接受任意数据类型的键名,为了确保他们在被存储到Map集合中之前不会被强制转换为其他数据类型,因而只能将它们放在数组中,因为这是唯一一种可以准确的呈现键名类型的方式。
4、同名属性碰撞
Map的键实际上是跟内存地址绑定的,只要内存地址不一样,就视为两个键。这就解决了同名属性碰撞(clash)的问题,扩展别人的库的时候,如果使用对象作为键名,就不用担心自己的属性与原作者的属性同名。
const map = new Map();
map.set(['a'], 555);
map.get(['a']); // undefined
上面代码的set和get方法,表面是针对同一个键,但实际上这是两个值,内存地址是不一样的,因此get方法读取该键,返回undefined
const map = new Map();
const k1 = ['a']
const k2 = ['a']
map
.set(k1, 111)
.set(k2, 222);
map.get(k1) // 111
map.get(k2) // 222
5、遍历
Map结构原生提供三个遍历器生成函数和一个遍历方法
keys(): 返回键名的遍历器
values(): 返回键值的遍历器
entries(): 返回所有成员的遍历器
forEach(): 遍历Map的所有成员
注意:Map的遍历顺序就是插入顺序
const map = new Map([
['F', 'no'],
['T', 'yes']
])
for(let key of map.keys()) {
console.log(key);
}
// "F"
// "T"
for(let value of map.values()) {
console.log(value);
}
// "no"
// "yes"
for(let item of map.entries()) {
console.log(item[0], item[1]);
}
// "F" "no"
// "T" "yes"
// 或者
for(let [key, value] of map.entries()) {
console.log(key, value);
}
// "F" "no"
// "T" "yes"
// 等同于使用map.entries()
for(let [key, value] of map) {
console.log(key, value);
}
// "F" "no"
// "T" "yes"
// 等同于使用map.entries()
for(let [key, value] of map) {
console.log(key, value);
}
// "F" "no"
// "T" "yes"
上面代码最后的那个例子,表示Map结构的默认遍历器接口,就是entries方法map[Symbol.iterator] === map.entries // true
6、转为数组
Map结构转为数组结构,比较快速的方法是使用扩展运算符(…)
const map = new Map({
[1, 'one'],
[2, 'two'],
[3, 'three']
})
[...map.keys()] // [1, 2, 3]
[...map.values()] // ['one', 'two', 'trhee']
[...map.entries()] // [[1, 'one'], [2, 'two'], [3, 'three']]
[...map] // [[1, 'one'], [2, 'two'], [3, 'three']]
结合数组的map方法、filter方法,可以实现Map的遍历和过滤。
const map0 = new Map()
.set(1, 'a')
.set(2, 'b')
.set(3, 'c')
const map1 = new Map(
[...map0].filter(([k, v]) => k < 3
);
// 产生Map结构 {1 => 'a', 2 => 'b'}
const map2 = new Map(
[...map0].map((k, v) => [k * 2, '_' + v])
);
// 产生Map结构 {2 => '_a', 4 => '_b', 6 => '_c'}
7、forEach()
Map还有一个forEach方法,与数组的forEach方法类似,也可以实现遍历
const map = new Map([1, 'one'], [2, 'two'], [3, 'three']);
map.forEach(value, key, map) => {
// one 1 { 1 => "one", 2 => "two", 3 => "three" }
// two 2 { 1 => "one", 2 => "two", 3 => "three" }
// three 3 { 1 => "one", 2 => "two", 3 => "three" }
console.log(value, key, map);
}
注意:遍历过程中,Map会按照键值对插入Map集合的顺序将相应信息传入forEach()方法的回调函数;而在数组中,会按照数值型索引值的顺序依次传入回调函数
forEach方法还可以接受第二个参数,用来绑定this
const reporter = {
report: function(key, value) {
console.log("Key: %s, Value: %s", key, value);
}
}
map.forEach(function(value, key, map) {
this.report(key, value);
}, reporter)
上面代码中,forEach方法的回调函数的this,就指向reporter。