ES6新增数据结构Set,WeakSet,Map,WeakMap的使用解析

125 阅读5分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。

在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的区别:

  1. WeakSet中只能存放对象类型,而Set中能够存放任意类型;
  2. 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的常用方法:

  1. set(key, value):在Map中添加key、value,并且返回整个Map对象;
  2. get(key):根据key获取Map中的value;
  3. has(key):判断是否包括某一个key,返回Boolean类型;
  4. delete(key):根据key删除一个键值对,返回Boolean类型;
  5. clear():清空所有的元素;
  6. forEach(callback, [, thisArg]):通过forEach遍历Map;

Map也可以通过for of进行遍历。

WeakMap

和Map类型的另外一个数据结构称之为WeakMap,也是以键值对的形式存在的。

那么和Map有什么区别呢?

  1. WeakMap的key只能使用对象,不接受其他的类型作为key;
  2. WeakMap的key对对象想的引用是弱引用,如果没有其他引用引用这个对象,那么GC可以回收该对象;

WeakMap常见的方法有四个:

  1. set(key, value):在Map中添加key、value,并且返回整个Map对象;
  2. get(key):根据key获取Map中的value;
  3. has(key):判断是否包括某一个key,返回Boolean类型;
  4. 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
});