文章来源:juejin.cn/post/699310… 作者:小丞同学
Map
为什么用Map?
1.传统对象结构
Map本质上是一个键值对的集合。和传统对象结构相比,传统对象只能用字符串作为键名,这在使用上造成了很大的限制。
const data = {}
//element为节点对象
const element = document.querySelector('.node')
console.log(element) //输出div.node对象
console.log(element.toString())
//用点操作符不能有空格,所以采用中括号的形式给对象赋值
data[element] = 'objectData'
//输出objectData,说明在对象中存在[object HTMLDivElement]键名
console.log(data['[object HTMLDivElement]'])
上面带代码中,我们创建了一个对象并将一个节点对象作为它的键名,并进行了代码测试,首先验证了获取到的element节点为一个对象,再确定了经过toString方法转化后的结果,以这个值为键名成功的输出了value值objectData。
上面的代码证明了传统对象的键名会通过toString方法转化为字符串类型
注意:在我们访问对象成员的时,键名有空格时不能采用点访问,例如data.ab c 这是错误的。我们可以用data['ab c']的形式访问
2.Map结构
Map类似于对象,但是键名不限于字符串,可以说Object结构提供键值对应,Map提供值值对应,因此采用Map结构会优于传统对象。
const dataMap = new Map()
const element = document.querySelector('.node')
dataMap.set(element,'objectData')
console.log(dataMap.get(element))
console.log(dataMap)
上面的代码中我们获取值时直接传入了element对象,成功将对象作为键名,弥补了传统对象的不足。
3.Map的特点
- Map默认情况下不包含任何键,所有键都是自己添加进去的。不同于Object原型链上有一写默认的键。
- Map的键可以时任何类型数据,就连函数都可以。
- Map的键值对个数可以轻易通过size属性获取,Object需要手动计算。
- Map在频繁增删键值对的场景下性能比Object更好。
4.什么时候用Map
- 想要添加的键值名和Object上的默认键值名冲突,又不想改名,用Map。
- 需要String和Symbol以外的数据类型做键值时,用Map。
- 键值对很多,有时需要计算数量,用Map。
- 需要频繁地增删键值对时,用Map。
Map实例属性和方法
- set set方法设置键名key对应的键值value,然后会返回整个Map结构,如果设置的key已存在,则会更新value值,否则会新生成该键。
也可以采用链式写法设置多次数据
- get 通过get方法获取key对应的value,如果传入的key不存在,则会返回undefined
- has 判断传入的键是否存在于当前Map对象中,该方法会返回一个布尔值
4.delete 删除传入的键,返回true,如果失败,则会返回false。
- clear 清除所有成员,没有返回值。
遍历方法
可以采用for...of和forEach两种方法。由于Map实例会维护键值对的插入顺序,因此可以根据插入顺序进行遍历
- for...of
- keys():返回键名的遍历器
- values():返回键值的遍历器
- entries():返回键值对的遍历器
- forEach():使用回调函数遍历每个成员
map.entries()
在Map实例中有一个迭代器,能以插入顺序生成[key,value]形式的数据。
我们可以通过entries方法来获得这个迭代器,从而利用for...of进行遍历
也可以这样
又entries是默认的迭代器,所以可以对Map实例采用扩展或者直接采用map
采用扩展操作
map.values()
map.keys()
forEach()
Map类型转化
- Map转化为数组
let map = new Map()
let arr = [...map]
- 数组转化为Map
let map = new Map(arr)
- Map转对象
let obj = {}
for(let [k,v] of map){
obj[k] = v
}
- 对象转为Map
for(let k of Object.keys(obj)){
map.set(k,obj[k])
}
WeakMap
什么是WeakMap
WeakMap是ES6中新增的一种集合类型,叫做'弱映射'。它和Map是兄弟关系,与Map的区别在于这个弱字,API还是Map那套API
WeakMap的特性
1. WeakMap只能将对象作为键名
只接受对象作为键名(null除外),不接受其它类型的值作为键名。
2.WeakMap的键名引用的对象是弱引用
首先我们需要知道什么是强引用什么是弱引用
强引用
const e1 = document.getElementById('foo')
const e2 = document.getElementById('bar')
const arr = [
[e1,'foo'],
[e2,'bar'],
];
上面的代码中e1和e2是两个对象,通过arr数组对这两个对象添加一些文字说明。但是这样就形成了arr对e1和e2的引用,而这种引用又是强引用。它的区别就体现在这。当我们不再需要这两个对象时,我们必须手动删除这个引用,接触arr对两个对象的引用关系,否则垃圾回收机制不会释放e1和e2占用的内存。因为arr仍然存在着对对象的引用。
arr[0] = null;
arr[1] = null;
弱引用
是指不能确保其引用的对象不会被垃圾回收器回收的引用。一个对象若只被弱引用所引用,则被认为是不可访问的,并因此可能在任何时刻被回收。 也就是说当我们创建一个弱引用的对象时,我们就可以静静地等待其被垃圾回收器回收。
总的来说,局势WeakMap保持了对键名所引用对象的弱引用,即垃圾回收机制不将该引用考虑在内。只要所引用的对象的其它引用都被清除,垃圾回收机制就会释放该对象所占用的内存。也就是说,一旦不再需要,WeakMap里面的键名对象和所对应的键值对会自动消失,不需要手动删除引用。
3.不可遍历
正因为WeakMap对键名引用的对象是弱引用关系 ,因此WeakMap内部成员是会取决于垃圾回收机制有没有执行,运行前后成员个数很可能是不一样的,而垃圾回收机制的执行又是不可预测的,因此不可遍历。
Map和WeakMap区别
- Map的键可以是任意类型,WeakMap只接受对象作为键,不接受其它类型的值作为键
- Map的键实际上是跟内存地址绑定的,只要内存地址不一样,就视为两个键;WeakMap的键是弱引用,键所指向的对象是可以被垃圾回收,此时键是无效的。
- Map可以被遍历,WeakMap不能被遍历
WeakMap使用场景
1.DOM节点元数据
const m = new Map()
const loginButton = document.querySelector(#login')
m.set(loginButton, {disabled: true})
当上面代码执行以后,登录按钮从DOM树中被删除了,但由于Map对节点对象是强引用关系,仍然保存着对按钮的引用,所以会引起内存泄露。
const m = new WeakMap()
const loginButton = document.querySelector(#login')
m.set(loginButton, {disabled: true})
当采用WeakMap,当节点删除之后,引用计数为0,自动等待垃圾回收机制回收。
2.部署私有属性
利用弱映射,将内部属性设置为实例的弱引用对象,当实例删除时,私有属性也会随之消失,因此不会造成内存泄露
const _counter = new WeakMap()
const _action = new WeakMap()
class Countdown{
constructor(counter,action){
_counter.set(this,counter)
_action.set(this,action)
}
dec(){
let counter = _counter.get(this)
if(counter < 1) return
counter --
_counter.set(this,counter)
if(counter === 0){
_action.get(this)()
}
}
}
const c = new Countdown(2,()=>console.log('DONE'))
c.dec()
c.dec()
// DONE
3.数据缓存
当我们需要在不修改原有对象的情况下存储某些属性等,而又不想管理这些数据时,可以使用WeakMap
const cache = new WeakMap()
function countOwnKeys(obj){
if(cache.has(obj)){
return cache.get(obj)
}else{
const count = Object.keys(obj).length
cache.set(obj, count)
return count
}
}