这是我参与8月更文挑战的第5天,活动详情查看:8月更文挑战
Map
Map对象以插入的先后顺序,存储键值对(key-value)形式的数据,任意数据类型都可以作为key或者value。
和Object类似,每个键都必须是唯一的,如果键的数据类型是原始数据类型则进行值比较,对象类型则进行引用比较。
实例属性
size只读属性,返回键值对的个数
方法
set(key, value)添加键值对get(key)返回与这个键相关联的值clear()移除所有键值对delete(key)删除与这个键相等的键值对has(value)返回是否包含这个值entries()返回一个包含Map中所有键值对且以插入顺序排列的迭代器,迭代器的每个元素为[key, value]形式的数组forEach(callbackFn, thisArg)以插入顺序,每一个键值对的key和value分别作为第1和第2个参数执行callback方法keys()返回一个包含Map中所有键且以插入顺序排列的迭代器values()返回一个包含Map中所有值且以插入顺序排列的迭代器
Map vs Object
差异概述
- Map的键可以是任何数据类型,Object的键只能是字符串或者Symbol
- Map可以通过size获取元素的个数,Object则没有
- Map本身就是一个迭代器,可以直接进行迭代;Object则需要借助Object对象的静态方法(
keys、values、entries)去进行迭代 - Map序列化(
JSON.stringify)后变成了普通的Object导致丢失本身的数据类型,需要自己去实现,同理,字符串解析成JSON对象也是无法支持Map
插入操作
先放测试代码:
var m = new Map()
var now = performance.now()
for(let i = 0; i<10000; i++) {
m.set('m'+i, i)
}
console.log(performance.now() - now)
var o = {}
var now = performance.now()
for(let i = 0; i<10000; i++) {
o['o'+i] = i
}
console.log(performance.now() - now)
当插入个数在1万个,Map和Object的差异并不显著,基本都在3-5ms以内。
到了10万个,Map平均时长在50ms上下,大部分落在45-55ms的区间;Object平均时长60ms上下,大部分落在50-70ms的区间。
到了100万个,Map平均时长在590ms上下,绝大部分落在500-700ms的区间;Object平均时长650ms上下,大部分落在550-750ms的区间,偶尔出现超过850ms的情况。
到了500万个,Map平均时长3542.4ms,绝大部分落在3300-3700ms的区间;Object平均时长3851.2ms,大部分落在3100-4200ms的区间,偶尔出现超过4800ms的情况。
到了800万个,Map平均时长6004.2ms,绝大部分落在5500-6500ms的区间;Object平均时长9171ms,大部分落在8500-9500ms的区间,偶尔出现超过10500ms的情况。
超过1000万个的时候,Map的平均时长7582ms,Object则会非常频繁的导致浏览器假死,对比结束。
| 插入次数 | Map | Object |
|---|---|---|
| 10万 | 50ms上下 | 60ms上下 |
| 100万 | 590ms上下 | 650ms上下 |
| 500万 | 3542.4ms | 3851.2ms |
| 800万 | 6004.2ms | 9171ms |
结论:在频繁插入操作的情况,Map比Object性能表现更好,且耗时分布更加收敛。
查询操作
var m = new Map()
for(let i = 0; i<1000000; i++) {
m.set('m'+i, i)
}
var now = performance.now()
for(let j = 0; j<1000000; j++) {
m.get('m'+j)
}
console.log(performance.now() - now)
var o = {}
for(let i = 0; i<1000000; i++) {
o['o'+i] = i
}
var now = performance.now()
for(let j = 0; j<1000000; j++) {
o['o'+j]
}
console.log(performance.now() - now)
| 查询次数 | Map | Object |
|---|---|---|
| 10万 | 24.6ms | 41.9ms |
| 50万 | 52.4ms | 170.5ms |
| 100万 | 83.2ms | 354.6ms |
| 500万 | 336.9ms | 2162.2ms |
结论:在频繁查询操作的情况,Map比Object性能表现更好,十万级别的耗时差距在2-4倍,百万级别的耗时差距在4-6倍左右
常见操作
初始化赋值
使用二维数组进行初始化赋值,形如 [[key0, value0], [key1, value1], ...]
var m = new Map([[123, 'abc'], ['aaa', true]])
console.log(m)
// output: {123 => "abc", "aaa" => true}
克隆
从一个Map对象克隆一个新的Map对象,可以从下面的代码看出,对象数据类型的key,即使对象内容变化了,也不会影响查询。同样,对象数据类型的value,也只是克隆了对象的指针。
var o = {name: 'Jack'}
var o2 = {msg: 'hello'}
var m = new Map([['k', o2], ['a', 123]])
m.set(o, [1, 2, 3])
var m2 = new Map(m)
o2.msg = 'hi'
console.log(m)
// output: {"k" => {…}, "a" => 123, {…} => Array(3)}
console.log(m2)
// output: {"k" => {…}, "a" => 123, {…} => Array(3)}
console.log(m === m2)
// output: false
console.log(m.get('k') === m2.get('k'))
// output: true
console.log(m.get('k'))
// output: {msg: 'hi'}
m.delete('k')
m2.set('k', 'v')
console.log(m)
// output: {"a" => 123, {…} => Array(3)}
console.log(m2)
// output: {"k" => "v", "a" => 123, {…} => Array(3)}
o.name = 'David'
console.log(m.get(o))
// output: [1, 2, 3]
console.log(m2.get(o))
// output: [1, 2, 3]
合并
两个Map的合并,在创建的的时候不能直接作为参数传入,需要转换成二维数组,且相同的键,以后面的为准。
var m = new Map([['x', 'abc'], ['y', 233]])
var m2 = new Map([['x', 666], ['z', 'hhh']])
var m3= new Map(m, m2)
var m4 = new Map([...m, ...m2])
console.log(m3)
// output: {"x" => "abc", "y" => 233}
console.log(m4)
// output: {"x" => 666, "y" => 233, "z" => "hhh"}
WeakMap
和Map不同,WeakMap对象所存储的键值对(key-value)中的键,只可以是对象数据类型。
尝试插入非对象数据类型的键时,会触发报错。
Uncaught TypeError: Invalid value used as weak map key
at WeakMap.set (<anonymous>)
at <anonymous>:1:3
WeakMap vs Map的差异
两者的差异主要集中在两点:
- WeakMap的key只能存储对象,而Map无论key还是value,都可以存储任意数据类型
- WeakMap存储的对象,如果没有其他的引用的话,这个对象将会被垃圾回收。这就是冠以 Weak 的原因,同时也意味着,WeakMap是不可枚举的,也就没有size。
在Chrome的开发者工具Console面板试验以下代码:
方法
相对于Map,WeakMap的弱引用特性导致它的方法只有以下4个:
set(key, value)添加键值对get(key)返回与这个键相关联的值delete(key)删除与这个键相等的键值对has(value)返回是否包含这个值
实用场景
- 对DOM进行操作并持有DOM节点,使用WeakMap可以使得DOM节点被其他代码逻辑删除了之后,可以方便内存被回收,防止内存泄漏
- Babel编译ES6下类的私有属性,参考我之前的一篇文章TypeScript小状况之对象的私有字段里面的其中一部分
兼容性
最后,来看看浏览器兼容情况。
Map
WeakMap
基本上,绝大部分的现代浏览器都支持Map和WeakMap,绝大部分使用场景都可以放心使用。