JavaScript 中有了Object 为什么还需要 Map 呢?
小知识,大挑战!本文正在参与“程序员必备小知识”创作活动。
众所周知,Map
是用于存储键值对的,而 JavaScript 中对象也是由键值对组成的,那么 Map
存在的意义是什么呢?
别把对象当 Map
1、可能通过原型链访问到未定义的属性
假设现有场景,开发一个网站,需要提供日语、汉语、韩语三种语言,我们可以定义一个字典去管理。
```js const dictionary = { 'ja': { 'Ninjas for hire': '忍者を雇う', }, 'zh': { 'Ninjas for hire': '忍者出租', }, 'ko': { 'Ninjas for hire': '고용 닌자', } }
console.log(dictionary.ja['Ninjas for hire']) // 忍者を雇う console.log(dictionary.zh['Ninjas for hire']) // 忍者出租 console.log(dictionary.ko['Ninjas for hire']) // 고용 닌자 ```
这样我们就把不同语言的字典管理起来了。但是,当我们试图访问 constroctor 属性,问题就出现了。
js
console.log(dictionary.ko['constructor']) // ƒ Object() { [native code] }
对于不存在的属性,我们期望得到 undefined
,结果却通过原型链访问到了未定义的属性,原型对象的 constructor
属性,指向构造函数。
此处有一个解决办法是把原型设置为 null
js
Object.setPrototypeOf(dictionary.ko, null)
console.log(dictionary.ko['constructor']) // undefined
2、对象的 Key 只能是字符串
假设需要将对象的 key
映射为 html 节点。我们写如下代码:
```js /* html部分
*/
const firstElement = document.getElementById('firstElement') const secondElement = document.getElementById('secondElement')
const map = {}
map[firstElement] = { data: 'firstElement' } map[secondElement] = { data: 'secondElement' }
console.log(map[firstElement].data) // secondElement console.log(map[secondElement].data) // secondElement
```
第一个元素的数据被覆盖了,原因是对象中的 key
只能是字符串类型,当我们没有使用字符串类型时,它会隐式调用 toString()
函数进行转换。于是两个 html 元素都被转为字符串 [object HTMLDivElement]
。
使用 Map
1、Map 常用操作
Map 可以使用任何 JavaScript 数据类型作为键
js
function People(name) {
this.name = name
}
const zhangsan = new People('zhangsan')
const xiaoming = new People('xiaoming')
const lihua = new People('lihua')
// 创建 Map
const map = new Map()
// 创建 Map 并进行初始化
const map1 = new Map([
['key1', 'val1'],
['key2', 'val2'],
])
// 设置键值映射关系
map.set(zhangsan, {
region: 'HB'
})
map.set(xiaoming, {
region: 'HN'
})
// 根据 key 获取对应值
console.log(map.get(zhangsan)) // { region: 'HB' }
console.log(map.get(xiaoming)) // { region: 'HN' }
// 获取不存在的 key 得到 undefined
console.log(map.get(lihua)) // undefined
// 通过 has 函数判断指定 key 是否存在
console.log(map.has(lihua)) // false
console.log(map.has(xiaoming)) // true
// map存储映射个数
console.log(map.size) // 2
// delete 删除 key
map.delete(xiaoming)
console.log(map.has(xiaoming)) // false
console.log(map.size) // 1
// clear 清空 map
map.clear()
console.log(map.size) // 0
2、遍历 Map
Map 可以确保遍历的顺序和插入的顺序一致
```js const zhangsan = { name: 'zhangsan' } const xiaoming = { name: 'xiaoming' } const map = new Map() map.set(zhangsan, { region: 'HB' }) map.set(xiaoming, { region: 'HN' })
for (let item of map) { // = for (let item of map.entries()) { console.log(item) } // 每个键值对返回的是 [key, value] 的数组 // [ { name: 'zhangsan' }, { region: 'HB' } ] // [ { name: 'xiaoming' }, { region: 'HN' } ] for (let key of map.keys()) { console.log(key) } // 遍历 key // { name: 'zhangsan' } // { name: 'xiaoming' } for (let key of map.values()) { console.log(key) } // 遍历 value // { region: 'HB' } // { region: 'HN' } ```
3、Map 中判断 key 相等
Map 内部使用 SameValueZero 比较操作。
关于SameValue 和 SameValueZero
SameValue (Object.is()
) 和严格相等(===
)相比,对于 NaN
和 +0
,-0
的处理不同
js
Object.is(NaN, NaN) // true
Object.is(0, -0) // false
SameValueZero 与 SameValue 的区别主要在于 0
与 -0
是否相等。
js
map.set(NaN, 0)
map.set(0, 0)
console.log(map.has(NaN)) // true
console.log(map.has(-0)) // true
Map 和 Object 的性能差异
- 内存占用
不同浏览器的情况不同,但给定固定大小的内存,Map
大约可以比 Object
多存储 50%
的键/值对。
- 插入性能
Map
略快,如果涉及大量操作,建议使用 Map
。
- 查找速度
性能差异极小,但如果只包含少量键/值对,则 Object
有时候速度更快。Object
作为数组使用时浏览器会进行优化。如果涉及大量查找操作,选择 Object
会更好一些。
- 删除性能
如果代码涉及大量的删除操作,建议选择 Map
。
参考资料
- 《JavaScript 忍者秘籍(第二版)》
- 《JavaScript 高级程序设计(第四版)》