本文已参与「新人创作礼」活动,一起开启掘金创作之路。
在ES6之前,我们存储数据的结构主要有两种:数组、对象。在ES6中新增了另外两种数据结构:Set、Map,以及它们的另外形式WeakSet、WeakMap。
Set
- Set是一个新增的数据结构,可以用来保存数据,类似于数组,但是和数组的区别是元素不能重复。
- 创建Set我们需要通过Set构造函数(暂时没有字面量创建的方式)。
- Set中存放的元素是不会重复的(可以用来给数组去重);
常见属性:
size: 返回Set中元素的个数;
常见方法:
add(value):添加某个元素,返回Set对象本身;delete(value):从set中删除和这个值相等的元素,返回boolean类型;has(value):判断set中是否存在某个元素,返回boolean类型;clear():清空set中所有的元素,没有返回值;forEach(callback, [, thisArg]):通过forEach遍历set;
另外Set是支持for of的遍历的。
WeakSet
和Set类似的另外一个数据结构称之为WeakSet,也是内部元素不能重复的数据结构。
WeakSet和Set的区别:
- WeakSet中只能存放对象类型,而Set中能够存放任意类型;
- WeakSet中对于对象是弱引用;
所谓的“弱引用”,就是JS引擎的GC在对对象进行回收的时候,不会考虑这个弱引用对于对象的引用,如果这个对象已经不存在其它的强引用了,那么GC就会在某个合适的时间将这个对象进行回收。
WeakSet常见的方法:
add(value):添加某个元素,返回WeakSet对象本身;delete(value):从WeakSet中删除和这个值相等的元素,返回boolean类型;has(value):判断WeakSet中是否存在某个元素,返回boolean类型;
注意:WeakSet不能遍历,因为WeakSet只是对对象的弱引用,如果我们遍历获取到其中的元素,那么有可能造成对象不能正常的销毁,所以存储到WeakSet中的对象是没办法获取的。
const wset = new WeakSet()
// wset.add(10) // TypeError: Invalid value used in weak set
const obj = { name: 'lzh' }
wset.add(obj)
console.log(wset); // WeakSet { <items unknown> }
WeakSet的应用场景:
// WeakSet的应用场景
const pWset = new WeakSet()
class Person {
constructor() {
pWset.add(this)
}
running() {
if (!pWset.has(this)) {
throw new Error('running方法只能被new Person创建出来的对象使用!')
}
console.log('running', this);
}
}
let p1 = new Person()
p1.running() // running Person {}
p1.running.apply({ name: 'hhh' }) // throw new Error('running方法只能被new Person创建出来的对象使用!')
p1 = null // 之后GC会对p1原先指向的那个对象进行回收
Map
另外一个新增的数据结构是Map,用于存储映射关系。
之前我们使用对象来存储这种映射关系,那么它跟Map的区别在哪里呢?
答:对象存储映射关系只能用字符串(ES6新增了Symbol)作为属性名(key);在某些情况下我们希望能通过其他类型作为key,比如对象,这个时候如果采用对象来存储的话,在JS内部会自动将对象转成字符串来作为key(对象转成字符串都是处理成'[object Object]');这种情况下,我们就可以使用Map。
const obj = {}
const info = { name: 'info'}
obj[info] = 'hhh'
console.log(obj); // { '[object Object]': 'hhh' }
const other = { name: 'other' }
obj[other] = 'xixixi'
console.log(obj); // { '[object Object]': 'xixixi' }
Map的常用方法:
set(key, value):在Map中添加key、value,并且返回整个Map对象;get(key):根据key获取Map中的value;has(key):判断是否包括某一个key,返回Boolean类型;delete(key):根据key删除一个键值对,返回Boolean类型;clear():清空所有的元素;forEach(callback, [, thisArg]):通过forEach遍历Map;
Map也可以通过for of进行遍历。
WeakMap
和Map类型的另外一个数据结构称之为WeakMap,也是以键值对的形式存在的。
那么和Map有什么区别呢?
- WeakMap的key只能使用对象,不接受其他的类型作为key;
- WeakMap的key对对象想的引用是弱引用,如果没有其他引用引用这个对象,那么GC可以回收该对象;
WeakMap常见的方法有四个:
set(key, value):在Map中添加key、value,并且返回整个Map对象;get(key):根据key获取Map中的value;has(key):判断是否包括某一个key,返回Boolean类型;delete(key):根据key删除一个键值对,返回Boolean类型;
注意:WeakMap也是不能遍历的,因为没有forEach方法,也不支持通过for of的方式进行遍历。
Weak的实际应用场景:响应式原理实现中对象的依赖收集数据结构
// WeakMap的应用场景:响应式原理实现中对象的依赖收集
const obj1 = { name: 'obj1', count: 20}
const obj2 = { name: 'obj2', count: 40 }
// obj1对象的依赖
function obj1NameFn1() {
console.log('obj1NameFn1');
}
function obj1NameFn2() {
console.log('obj1NameFn2');
}
function obj1CountFn1() {
console.log('obj1CountFn1');
}
function obj1CountFn2() {
console.log('obj1CountFn2');
}
// obj2对象的依赖
function obj2NameFn1() {
console.log('obj2NameFn1');
}
function obj2NameFn2() {
console.log('obj2NameFn2');
}
function obj2CountFn1() {
console.log('obj2CountFn1');
}
function obj2CountFn2() {
console.log('obj2CountFn2');
}
const targetMap = new WeakMap()
// 收集依赖结构
const obj1DependMap = new Map()
obj1DependMap.set('name', [obj1NameFn1, obj1NameFn2])
obj1DependMap.set('count', [obj1CountFn1, obj1CountFn2])
targetMap.set(obj1, obj1DependMap)
const obj2DependMap = new Map()
obj2DependMap.set('name', [obj2NameFn1, obj2NameFn2])
obj2DependMap.set('count', [obj2CountFn1, obj2CountFn2])
targetMap.set(obj2, obj2DependMap)
// 如果对象属性发生变更,重新执行该属性相关的依赖(通过Objec.defineProperty/Proxy来设置监听自动执行)
obj1.count++
const depends = targetMap.get(obj1).get('name')
depends.forEach(depend => {
depend() // obj1NameFn1 obj1NameFn2
});