JavaScript 中有了Object 为什么还需要 Map 呢
目录
- 一、别把对象当 Map
- 1、可能通过原型链访问到未定义的属性
- 2、对象的 Key 只能是字符串
- 二、使用 Map
- 1、Map 常用操作
- 2、遍历 Map
- 3、Map 中判断 key 相等
- 4、复制或合并 Map
- 5、Map 序列化
- 三、Map 和 Object 的性能差异
一、别把对象当 Map
1、可能通过原型链访问到未定义的属性
假设现有场景,开发一个网站,需要提供日语、汉语、韩语三种语言,我们可以定义一个字典去管理。
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
属性,问题就出现了。
console.log(dictionary.ko['constructor']) // ƒ Object() { [native code] }
对于不存在的属性,我们期望得到 undefined
,结果却通过原型链访问到了未定义的属性,原型对象的 constructor
属性,指向构造函数。
此处有一个解决办法是把原型设置为 null
Object.setPrototypeOf(dictionary.ko, null) console.log(dictionary.ko['constructor']) // undefined
2、对象的 Key 只能是字符串
假设需要将对象的 key
映射为 html
节点。我们写如下代码:
/* html部分 <div id="firstElement"></div> <div id="secondElement"></div> */ 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]
。
对象的键也可以为 Symbol
,不过在 for..in
遍历和 Object.keys()
以及用 JSON.stringify()
进行序列化的时候,都会忽略为 Symbol
的键。
二、使用 Map
1、Map 常用操作
Map
可以使用任何 JavaScript
数据类型作为键
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 并进行初始化 将二维键值对数组转换成一个Map对象 const map1 = new Map([ ['key1', 'val1'], ['key2', 'val2'], ]) // 将 Map 转为二维数组 console.log(Array.from(map1)) // [ [ '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 可以确保遍历的顺序和插入的顺序一致
const zhangsan = { name: 'zhangsan' } const xiaoming = { name: 'xiaoming' } const map = new Map() map.set(zhangsan, { region: 'HB' }) map.set(xiaoming, { region: 'HN' }) // 每个键值对返回的是 [key, value] 的数组 for (let item of map) { // = for (let item of map.entries()) { console.log(item) // [ { name: 'zhangsan' }, { region: 'HB' } ] // [ { name: 'xiaoming' }, { region: 'HN' } ] } // 遍历 key for (let key of map.keys()) { console.log(key) // { name: 'zhangsan' } // { name: 'xiaoming' } } // 遍历 value for (let key of map.values()) { console.log(key) // { region: 'HB' } // { region: 'HN' } } // 使用 forEach() 方法迭代 Map map.forEach(function(value, key) { console.log(key, value) // { name: 'zhangsan' } { region: 'HB' } // { name: 'xiaoming' } { region: 'HN' } })
3、Map 中判断 key 相等
Map
内部使用 SameValueZero
比较操作。
关于SameValue
和 SameValueZero
SameValue (Object.is()
) 和严格相等(===)相比,对于 NaN 和 +0,-0 的处理不同
Object.is(NaN, NaN) // true Object.is(0, -0) // false
SameValueZero
与 SameValue
的区别主要在于 0 与 -0 是否相等。
map.set(NaN, 0) map.set(0, 0) console.log(map.has(NaN)) // true console.log(map.has(-0)) // true
4、复制或合并 Map
Map 能像数组一样被复制
let original = new Map([ [1, {}] ]) let clone = new Map(original) // 克隆 Map console.log(clone.get(1)); // {} console.log(original === clone) // false console.log(original.get(1) === clone.get(1)) // true
多个 Map 合并
let first = new Map([ [1, 'one'], [2, 'two'], [3, 'three'], ]); let second = new Map([ [1, 'uno'], [2, 'dos'] ]); // 合并两个 Map 对象时,如果有重复的键值,则后面的会覆盖前面的。 // 展开运算符本质上是将 Map 对象转换成数组。 let merged = new Map([...first, ...second]); console.log(merged.get(1)); // uno console.log(merged.get(2)); // dos console.log(merged.get(3)); // three
5、Map 序列化
Map
无法被序列化,如果试图用 JSON.stringify
获得 Map 的 JSON 的话,只会得到 "{}"。
由于 Map 的键可以是任意数据类型,而 JSON 仅允许将字符串作为键,所以一般情况下无法将 Map 转为 JSON。
不过可以通过下面的方式去尝试序列化一个 Map:
// 初始化 Map(1) {"key1" => "val1"} const originMap = new Map([['key1', 'val1']]) // 序列化 "[[\"key1\",\"val1\"]]" const mapStr = JSON.stringify(Array.from(originMap.entries())) // 反序列化 Map(1) {"key1" => "val1"} const cloneMap = new Map(JSON.parse(mapStr))
三、Map 和 Object 的性能差异
内存占用
不同浏览器的情况不同,但给定固定大小的内存,Map
大约可以比 Object
多存储 50% 的键/值对。
插入性能
Map 略快,如果涉及大量操作,建议使用 Map
。
查找速度
性能差异极小,但如果只包含少量键/值对,则 Object
有时候速度更快。Object
作为数组使用时浏览器会进行优化。如果涉及大量查找操作,选择 Object 会更好一些。
删除性能
如果代码涉及大量的删除操作,建议选择 Map
。
到此这篇关于JavaScript
中有了Object
为什么还需要 Map 呢的文章就介绍到这了,更多相关JavaScript Map
内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!