map数据结构就是js中的hash表达,可以提供非常快速的插入-删除-查找操作,无论多少数据,插入和删除只需要接近常量的时间即O(1)

由来?,参数?,基本用法?

// JavaScript 的对象(Object),本质上是键值对的集合(Hash 结构),但是传统上只能用字符串当作键。这给它的使用带来了很大的限制。
let obj = {sex: '男'}
let kk = {}
kk[obj] = 'kk'
console.log(kk) // { '[object Object]': 'kk' }
console.log(obj.toString()) // [object Object]
console.log(kk['[object Object]']) // kk

// 为了应对以上问题 map诞生
// ES6 提供了 Map 数据结构。它类似于对象,也是键值对的集合,但是“键”的范围不限于字符串,各种类型的值(包括对象)都可以当作键。也就是说,
// Object 结构提供了“字符串—值”的对应,Map 结构提供了“值—值”的对应,是一种更完善的 Hash 结构实现。如果你需要“键值对”的数据结构,Map 比 Object 更合适。

// 自我理解: 普通的对象,键就是一个具体的值,但是map的键可以是一个引用

console.log('---------------------')

// map 的set,get,has,delete方法(添加成员,删除成员)
const m = new Map()
const o = {hello: 'hello world'}
m.set(o, '你好')
m.get(o)
console.log(m.get(o))  // 你好
console.log(m.has(o)) // true
console.log(m.delete(o)) // true
console.log(m.has(o)) // false

// map也可以接受一个数组作为参数
const n = new Map([['name','this is name'],['age', 'this is age']])
console.log(n.size)  // 2
console.log(n.get('name')) // this is name
console.log(n.get('age')) // this is age
console.log(n.has('age')) //true
// map 接收一个数组未参数,实际执行的是如下的算法
const a = [['name','this is name'],['age', 'this is age']]
const b = new Map()
a.forEach(([key, value]) => {b.set(key,value)})
console.log(b.size) // 2
console.log(b.has('name')) // true
console.log(b.get('age')) //  this is age

// map可接收参数扩展:事实上,不仅仅是数组,任何具有 Iterator 接口、且每个成员都是一个双元素的数组的数据结构(详见《Iterator》一章)
// 都可以当作Map构造函数的参数。这就是说,Set和Map都可以用来生成新的 Map。
const c = new Set([['name', 'kate'],['sex', 'woman']])
const c1 = new Map(c)
console.log(c1.size)  // 2
console.log(c1.get('name')) // kate
console.log(c1.has('sex')) // true
const d = new Map([['name', 'kateother'],['sex', 'woman']])
const d1 = new Map(d)
console.log(d1.size) // 2
console.log(d1.get('name')) //  kateother
console.log(d1.get('sex')) // woman


// 注意(当键是对象的时候):只有对同一个对象的引用,map结构才会将其看做同一个键
const e = new Map()
e.set(['A'], 666)
console.log(e.get(['A'])) // undefined  set和get方法,表面是针对同一个键,但实际上这是两个不同的数组实例,内存地址是不一样的,因此get方法无法读取该键,返回undefined。
const e1 = ['A']
e.set(e1,'AAAAAAAAAAAAA')
console.log(e.get(e1))  // AAAAAAAAAAAAA
// Map 的键实际上是跟内存地址绑定的,只要内存地址不一样,就视为两个键。
// 这就解决了同名属性碰撞(clash)的问题,我们扩展别人的库的时候,如果使用对象作为键名,就不用担心自己的属性与原作者的属性同名

// Map的键如果是一个简单类型的值(数字,字符串,布尔值),只要两个值严格相等,map将其视为一个键。(注意:尽管NaN不严格相等于自身,但map将其视为同一个键)
console.log(+0 === -0) // true
let f = new Map()
f.set(-0, '000')
console.log(f.get(+0)) // 000
f.set(undefined, 'uuuuuu')
f.set(null, 'nnnnnnnnnn')
console.log(f.get(undefined)) // uuuuuu
console.log(f.get(null)) // nnnnnnnnnn
f.set(NaN, 'NNNNN')
f.set(NaN, 'MMMMMMM')
console.log(f.get(NaN)) // MMMMMMM

实例的属性和操作方法

// map结构的实例有以下的属性和操作方法
const a = new Map()
a.set('a', 'A')
a.set('b', 'B')
console.log(a.size) // 2

// Map.prototype.set(key,value)
// set方法设置键名key对应的键值为value,返回的是整个map结构,如果key已经有值,就会去更新,没有就会去创建
// set方法返回的是Map对象,所以可以使用链式写法
let b = a.set('c','C')
console.log(b) //  Map(3) { 'a' => 'A', 'b' => 'B', 'c' => 'C' }
let c = new Map().set(1,'a').set(2,'b')
console.log(c) // Map(2) { 1 => 'a', 2 => 'b' }

// Map.prototype.get(key)
// get方法根据键名获取值,如果找不到就返回undefined
console.log(a.get('a')) // A
console.log(a.get('i')) // undefined

// Map.prototype.has(key) 
// has方法返回一个布尔值,根据key值取查找是否存在与整个map对象当中,返回值是布尔值,存在还是不存在
console.log(a.has('a')) // true
console.log(a.has('i')) // false

console.log('-------------')
// Map.prototype.delete(key)
// delete方法会根据key值删除当前map对象中的这一项,返回值表示是否删除成功
const flag =  a.delete('a')
console.log(flag) // true
console.log(a.has('a')) // false

// Map.prototype.clear()
// clear 顾名思义是清除所有的成员,没有返回值
let d = new Map().set(1,1).set(2,2)
console.log(d.size) // 2
d.clear()
console.log(d.size) // 0

Map的循环方案:

// Map结构提供三个遍历器生成函数和一个遍历方法
// 需要特别注意的是,Map的遍历顺序就是插入顺序
// Map.prototype.keys()
// Map.prototype.values()
// Map.prototype.entries()
// Map.prototype.forEach()

const map = new Map([[1,'1'],[2,'2'],[3,'3'],[4,'4'],[5,'5']])
console.log(map)

for(let key of map.keys()) {
  console.log(key) // 12345 数字
}
for(let value of map.values()) {
  console.log(value) // 12345 字符串
}

for(let item of map.entries()) {
  console.log(item)
// [ 1, '1' ]
// [ 2, '2' ]
// [ 3, '3' ]
// [ 4, '4' ]
// [ 5, '5' ]
}
// 可以结构的
for(let [a, b] of map.entries()) {
  console.log(a,b)
// 1 1
// 2 2
// 3 3
// 4 4
// 5 5
}
// 等同于使用map.entries()
for (let [key, value] of map) {
  console.log(key, value);
// 1 1
// 2 2
// 3 3
// 4 4
// 5 5
}
// map 结构的默认遍历器接口(Symbol.iterator属性),就是entries方法
console.log(map[Symbol.iterator] === map.entries)  // true

console.log(map.entries)  // [Function: entries]
console.log(map[Symbol.iterator]) // [Function: entries]
console.log(map.entries()) 
// [Map Entries] {
//   [ 1, '1' ],
//   [ 2, '2' ],
//   [ 3, '3' ],
//   [ 4, '4' ],
//   [ 5, '5' ]
// }

console.log('---扩展运算符---')
// 扩展运算符(…)内部使用for…of循环
console.log([...map]) //[ [ 1, '1' ], [ 2, '2' ], [ 3, '3' ], [ 4, '4' ], [ 5, '5' ] ]
console.log([...map.keys()])  // [ 1, 2, 3, 4, 5 ]
console.log([...map.values()]) // [ '1', '2', '3', '4', '5' ]
console.log([...map.entries()]) // [ [ 1, '1' ], [ 2, '2' ], [ 3, '3' ], [ 4, '4' ], [ 5, '5' ] ]

console.log('------利用map和filter对map进行过滤-------')
// 利用map和filter对map进行过滤
const kate = new Map([[1,'1'],[2,'2'],[3,'3'],[4,'4']])
console.log(kate)

const kate1 = new Map([...kate].filter(([kay,value]) => (value < 3)))
console.log(kate1)

const kate3 = new Map([...kate].map(([key,value]) => [key * 3, '*'+value]))
console.log(kate3) // Map(4) { 3 => '*1', 6 => '*2', 9 => '*3', 12 => '*4' }
 

const reporter = {
  report: function(key, value) {
    console.log("Key: %s, Value: %s", key, value);
  }
};
map.forEach(function(value, key, map) {
  console.log(this)
  this.report(key, value);
}, reporter);

// { report: [Function: report] }
// Key: 1, Value: 1
// { report: [Function: report] }
// Key: 2, Value: 2
// { report: [Function: report] }
// Key: 3, Value: 3
// { report: [Function: report] }
// Key: 4, Value: 4
// { report: [Function: report] }
// Key: 5, Value: 5

数组和map,对象和map,json和map之间的转换

const ff = new Map().set(1,'1').set(2,'2').set(3,'3')
// 转换为数组
console.log([...ff]) // [ [ 1, '1' ], [ 2, '2' ], [ 3, '3' ] ]
// 数组转换为map
console.log(new Map([[1,'1111'],[2,'222222']]))  // Map(2) { 1 => '1111', 2 => '222222' }

// 转换为对象  如果所有map的键都是字符串,它可以无损转换为对象
function objZmap(params) {
  let obj = {};
  for (let [key,value] of params) {
    obj[key] = value
  }
  return obj
}
const ffff1 = new Map().set('1',1).set('2',2)
console.log(objZmap(ffff1)) // { '1': 1, '2': 2 }
// 如果有非字符串的键名,那么这个键名会被转成字符串,再作为对象的键名。
const ffff12 = new Map().set(true,1).set(false,2)
console.log(objZmap(ffff12)) // { true: 1, false: 2 }
for (let a in objZmap(ffff12)) {
  console.log(typeof a)  // string
}
// 对象转换为map
const aa = {'age': 45, 'sex': '男'}
console.log(Object.entries(aa))  // [ [ 'age', 45 ], [ 'sex', '男' ] ]
// Object.entries()方法返回一个给定对象自身可枚举属性的键值对数组,
// 其排列与使用 for...in 循环遍历该对象时返回的顺序一致(区别在于 for-in 循环还会枚举原型链中的属性)。
console.log(new Map(Object.entries(aa)))  // Map(2) { 'age' => 45, 'sex' => '男' }

// map转换为json
// 如果键名是字符串就考虑转换为对象json  map键名为非字符串可以考虑转换为数组json
const nn = new Map().set('1',1).set('2',2)
console.log(JSON.stringify(objZmap(nn)))  // {"1":1,"2":2}
const mm = new Map().set(true,1).set(false,2)
console.log(JSON.stringify([...mm]))  // [[true,1],[false,2]]


// json 转换为 map
// 正常情况下,是对象形式的,且键名都还是字符串
console.log(JSON.parse('{"yes": true, "no": false}'))  // { yes: true, no: false }
console.log(new Map(Object.entries(JSON.parse('{"yes": true, "no": false}'))))  // Map(2) { 'yes' => true, 'no' => false }
// 有一种特殊情况,整个 JSON 就是一个数组,且每个数组成员本身,又是一个有两个成员的数组。这时,它可以一一对应地转为 Map。
console.log(JSON.parse('[[true,7],[{"foo":3},["abc"]]]')) // [ [ true, 7 ], [ { foo: 3 }, [ 'abc' ] ] ]
console.log(new Map(JSON.parse('[[true,7],[{"foo":3},["abc"]]]')))  // Map(2) { true => 7, { foo: 3 } => [ 'abc' ] }