一、Map是什么
Map的本质是一个对象:
const map = new Map()
console.log(Object.prototype.toString.call(map)) // [object Map]
注意点:
- Map可以保存一个键值对,和object不同的是,object的键只能是字符串(数字也会转为字符串类型)和symbol,而Map的键可以是任意值,并且Map中的键一定是唯一的:
const map = new Map([
['a', 1],
['a', 2], // 第二个'a'会覆盖第一个'a',Map中的键只能出现一次
[{}, 3] // Map可以用任何值当做键
])
console.log(map) // Map(2) {'a' => 2, {…} => 3}
- Map采用
零值相等算法进行比较,Map曾经使用同值相等(+0和-0是不同的两个值,符合Object.is)
这意味着,除了NaN(多个NaN作为键会覆盖前面的键),其他所有的键采用===判断是否相等,Set去重也是这个逻辑
const map = new Map([[NaN, 1]])
map.set(NaN, 2)
map.set(+0, 'a')
map.set(0, 'b')
console.log(map) // Map(2) {NaN => 2, 0 => 'b'}
二、如何创建Map
通过new调用Map构造函数,传入一个二维数组作为Map的初始值,通过set方法可以追加值。
const map = new Map([['a', 1]])
map.set(true, true)
map.set(1, 1)
console.log(map) // Map(3) {'a' => 1, true => true, 1 => 1}
三、Map实例的属性和方法
1、set:追加属性
const map = new Map([[1, 1]])
map.set('2', '2')
console.log(map) // Map(2) {1 => 1, '2' => '2'}
不要使用这种方式去追加map对象的属性:
const map = new Map()
map['a'] = 'a'
map['b'] = 'b'
console.log(map) // Map(0) {a: 'a', b: 'b', size: 0}
console.log(map.get('a')) // undefined
虽然不会报错,但是这样写没有实质意义,正确的方式是使用set或者使用Map构造函数时初始化一个二维数组
2、get:获取键对应的值
const map = new Map([[1, 1]])
console.log(map.get(1)) // 1
3、has:判断是否有某个键
const map = new Map([[1, 1]])
console.log(map.has(1)) // true
console.log(map.has('1')) // false
4、delete:删除某个键值对
const map = new Map([
[1, 1],
['2', 2]
])
map.delete(1)
console.log(map) // Map(1) {'2' => 2}
5、clear:清空所有的键值对
const map = new Map([
[1, 1],
['2', 2]
])
map.clear()
console.log(map) // Map(0) {size: 0}
6、size:获取map中有几个键值对
const map = new Map([
[1, 1],
['2', 2]
])
console.log(map.size) // 2
7、keys/values/entries
const map = new Map([
[1, 1],
['2', 2]
])
const keys = map.keys()
const values = map.values()
const entries = map.entries()
console.log(keys) // MapIterator {1, '2'}
console.log(values) // MapIterator {1, 2}
console.log(entries) // MapIterator {1 => 1, '2' => 2}
8、forEach:以插入的顺序遍历map
const map = new Map([
['a', 'a'],
[1, 1],
['2', 2]
])
map.forEach((value, key) => {
console.log(value, key)
})
四、Map和Object的比较
Map和Object相同的是,它们都允许你按键存取一个值、删除键、检测一个键是否绑定了值,因此在Map之前一直拿对象当做Map使用。但现在有了Map,以下情况使用Map会更好:
1、当做字典使用时,意外的键
Map默认不包含任何键,只包含显式插入的键
const arr = [
[0, '西游记'],
[1, '水浒传'],
[2, '红楼梦'],
[3, '三国演义']
]
const map = new Map(arr)
console.log(map) // Map(4) {0 => '西游记', 1 => '水浒传', 2 => '红楼梦', 3 => '三国演义'}
console.log(map.get(0)) // 西游记
以上字典的写法通过对象也可以实现:
const obj = {
0: '西游记',
1: '水浒传',
2: '红楼梦',
3: '三国演义'
}
console.log(obj[0])
但是,对象是有原型的,原型上的键名可能与你自己在对象上设置的键名重复。这样便存在了隐患,尽管这样的隐患很小,但是Map是不存在这样的隐患的
当然了,你可以这么做:
const obj = {
0: '西游记',
1: '水浒传',
2: '红楼梦',
3: '三国演义'
}
const noPrototypeObj = Object.create(null) // 没有原型的对象
Object.assign(noPrototypeObj, obj)
console.log(noPrototypeObj)
但是很少有人这样做
2、Map的键和对象的键
对象的键只能是字符串/数字,ES6提供了Symbol,Symbol也可以当做对象的键:
const name = Symbol()
const age = Symbol()
const obj = {
[name]: 'zhang',
grade: '高一',
1: 1
}
obj[age] = 18
console.log(obj) // {1: 1, grade: '高一', Symbol(): 'zhang', Symbol(): 18}
Map对象的键可以是任意值,可以是对象/函数等(并且Map的打印顺序完全按照插入顺序来):
const arr = [
[{}, 1],
[[], 2],
[function () {}, 3]
]
const map = new Map(arr)
console.log(map) // Map(3) {{…} => 1, Array(0) => 2, ƒ => 3}
3、键的顺序
Map对象中键是有序的,当遍历Map时,按照插入的顺序返回键值对:
const arr = [
[{}, 1],
[[], 2],
[function () {}, 3]
]
const map = new Map(arr)
console.log(map) // Map(3) {{…} => 1, Array(0) => 2, ƒ => 3}
for (const item of map) {
console.log(item)
}
// [{…}, 1]
// [Array(0), 2]
// [ƒ, 3]
对象的遍历首先想到使用for...in,它在遍历对象时的顺序:首先找到对象中的非负整数属性,将这一部分的属性按照升序遍历,再找到其他的属性,按照创建时的顺序遍历出来。参考:for in遍历的顺序
const name = Symbol()
const obj = {
[name]: 'zhang',
grade: '高一',
1: 1,
'-1': -1
}
console.log(obj) // {1: 1, grade: '高一', -1: -1, Symbol(): 'zhang'}
// for...in仅包含以字符串为键的属性
for (const key in obj) {
console.log(key, obj[key])
}
// 1 1
// grade 高一
// -1 -1
以上代码可以看出,for...in无法遍历Symbol键,它只能遍历以字符串为键的属性
同样的,Object.keys()也无法拿到Symbol键:
const name = Symbol()
const obj = {
[name]: 'zhang',
grade: '高一',
1: 1,
'-1': -1
}
console.log(obj) // {1: 1, grade: '高一', -1: -1, Symbol(): 'zhang'}
obj.__proto__.a = 'a'
Object.prototype.b = 'b'
const keys = Object.keys(obj)
console.log(keys) // ['1', 'grade', '-1']
与for...in相比,Object.keys()不会获取到原型上的属性
4、获取键值对的个数
Map可以通过size属性轻松获取键值对的个数,而对象需要手动计算
5、迭代
Map内部实现了迭代器,所以可以使用for...of进行迭代,并且Map对象自带forEach方法也可以进行迭代;
对象的迭代需要使用for...in/Object.keys()/Object.getOwnPropertyNames()/Object.getOwnPropertySymbols()等方式
6、性能
如果频繁地增删键值对,Map的性能表现更优,对象没有对此进行优化
7、序列化
Map不支持直接序列化,对象可以
五、Map和数组的相互转换
1、数组转为Map:使用Map构造函数可以将一个二维键值对数组转为Map对象
const arr = [
[1, 1],
['2', '2'],
[true, true]
]
const map = new Map(arr)
2、Map对象转为数组
const array = [...map]
const array1 = Array.from(map)
或者只想获取map的键集合:
const array2 = Array.from(map.keys())
六、遍历Map的方式
1、使用map自带的forEach
const map = new Map([
['a', 'a'],
[1, 'one'],
['2', 'two']
])
map.forEach((value, key) => {
console.log(key, value)
})
2、使用for...of,map内部实现了迭代器
const map = new Map([
['a', 'a'],
[1, 'one'],
['2', 'two']
])
for (const [key, value] of map) {
console.log(key, value)
}
如果只想遍历键:
for (const key of map.keys()) {
console.log(key)
}
如果只想遍历值:
for (const value of map.values()) {
console.log(value)
}
七、Map的实际用途
1、Map对象是很好的字典表
const userType = [
[1, 'OA'],
[2, '代维'],
[3, '外协']
]
const userTypeMap = new Map(userType)