详解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。