《Vuejs设计与实现》5.8.1 如何Set与Map

154 阅读2分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第17天,点击查看活动详情

前文中我们讲完了数组的响应,以及响应数组中的一些问题,今天我们来看看Map与Set。Vue2中对这两种数据结构支持并不好,我没记错的话仅仅是支持v-for时遍历。得益于vue3使用proxy去做响应式,我们终于可以实现对这两种数据结构完整的响应

Set

Set是ES6 提供的新数据结构,它类似于数组,但是成员的值不允许重复。也就是说它是一个天然的去重后的数组 它有如下几个API

  • add()
  • delete()
  • has()
  • clear()

Map

JavaScript 的对象(Object),本质上是键值对的集合(Hash 结构),但是传统上只能用字符串当作键。这给它的使用带来了很大的限制。

为了解决这个问题,ES6 提供了 Map 数据结构。它类似于对象,也是键值对的集合,但是“键”的范围不限于字符串,各种类型的值(包括对象)都可以当作键。也就是说,Object 结构提供了“字符串—值”的对应,Map 结构提供了“值—值”的对应,是一种更完善的 Hash 结构实现。如果你需要“键值对”的数据结构,Map 比 Object 更合适。

如果你需要查看一下API,你可以直接在这里看Set 和 Map 数据结构,下面我直接切入正题。

首先,MapSet{}|[]相比,它的API是有区别的.

const obj1 = {}
obj1.a = 1

const arr = []
arr[0] = 1

const map = new Map()
map.set('a',1)
map.get('a')

const set = new Set()
set.add(1)

API上的差异就导致,我们之前的代码不能正确的代理Map与Set.

代理Set时的出现的问题

在数组中,Array.length是一个属性值,是可以直接通过[[GET]]获取,但是如果你通过Proxy去获取Set.size,那么就会被报错。因为在ECMA中,Set.size其实是一个函数而不是属性。 解决办法也比较简单,首先我们封装一个获取变量类型的函数,然后当数据是Set并且是获取size的时候,不要改变this的指向,让this指向本身,自然能够正确执行

const getType = (anything)=>Object.prototype.toString.call(anything).split('[object ')[1].replace(']','').toLowerCase()

const s = new Proxy(new Set(),{
  get(target,key,receiver){
      if(getType(target) === 'set'&&key === 'size'){
          return Reflect.get(target,key,target)
      }
      /*省略其他逻辑*/
       return Reflect.get(target,key,receiver)
  }
})

于是,这时候我们再试一下delete,又会报错,问题还是调用delete时的this指向

const s = new Proxy(new Set(),{
  get(target,key,receiver){
      if(getType(target) === 'set'&&key === 'size'){
          return Reflect.get(target,key,target)
      }
      /*省略其他逻辑*/
       return target[key].bind(target)
  }
})