说说Vue3中Set/Map的处理|toRaw实现的一些思考和尝试,写篇文章分享,说说一个新奇的发现:Proxy不能直接拦截Set/Map!因为Set/Map的方法必须自己调用。看到这句话,你不禁想知道Vue是如何代理它们的。下面继续看调用方法的三个方法。本节讨论Set/Map实例方法的三种方法。这种调用方法未包含在他们的示例中。当然,自调用是可以正常运行的。在Proxy对象上调用Proxy代理一个集合,没有任何拦截,然后调用add方法,发送!constp=newProxy(newSet(),{})p.add(1)//TypeError:MethodSet.prototype.addcalledonincompatiblereceiver#无法运行该方法,但好消息是即Proxy可以拦截方法的读取,这是下面使用Proxy包裹Set/Map的基础constp=newProxy(newSet(),{get(target,key){console.log('get:',key)returnReflect.get(target,key)},})p.add(1)//get:add//TypeError:MethodSet.prototype.addcalledonincompatiblereceiver#在对象上调用即继承一个集合创建一个对象继承一个集合,尝试调用add方法,也发送!constobj=Object.create(newSet())obj.add(1)//TypeError:方法Set.prototype.add在不兼容的接收器上调用#constobj={}Object.setPrototypeOf(obj,newSet())obj.add(1)//TypeError:MethodSet.prototype.addcalledonincompatiblereceiver#在子类上调用时,是否无法在其他对象上调用Map/Set方法?还有一些,就是他们的子类例子letset=newmySet()set.add(1)//终于成功运行console.log(set)//mySet(1)[Set]{1}Result经过上面的实验,我们知道我们要拦截Set/Map,最简单的方法是为它们设置子类。但是Vue并没有采用这种方式,原因很简单。class的关键时期是IE13(Edge13),Vue想兼容IE12而这个特性babel也解决不了,就是padding不了。所以Vue3还是选择使用Proxy重写的方式来解决。接下来,让我们看看它是如何实现的。使用Proxy包裹Set来实现思路。由于Set/Map方法只能在原始对象上调用,所以我们封装了一组方法,先获取原始对象,然后在它们上面调用方法,就像下面的constp=newProxy(newSet(),{get(target,key){if(key==='add')returnadd//返回自己实现的方法returnReflect.get(target,key)},})functionadd(value){constrawTarget=toRaw(this)//获取代理的原始对象rawTarget.add(value)//在原始对象上调用add方法returnthis//add方法将返回集合本身}toRaw是一个api实现vue,用于获取代理对象的原始对象,实现toRawtoRaw也很简单。毕竟代理对象的拦截器是我们自己写的。只需要在里面定义一个特殊的属性,让拦截器返回原来的对象即可。constp=newProxy(newSet(),{get(target,key){if(key==='__v_raw')returntarget//访问特殊属性,返回原始对象if(key==='add')returnadd//返回自己实现的方法returnReflect.get(target,key)},})//获取原始对象的方法functiontoRaw(p){returnp['__v_raw']}Vue认为多层代理嵌套的问题,所以在源码中递归调用toRaw的实现,直到对象不带'__v_raw'属性,实现toRaw后,add函数已经可以正常运行p.add(1)p。add(2)console.log(p)//Proxy{1,2}浏览器控制台输出//Set(2){1,2}节点控制台输出Vue就是通过这个方法来实现Set/Map代理在Vue中的具体实现。这里是Vue3的一部分源码,主要是在reactive方法中对Set/Map的特殊处理。展开或修改部分函数调用,但逻辑??不变functionreactive(target){letproxy//代理对象consttype=Object.prototype.toString.call(target)//获取类签名//对Set和Map特殊处理if(type==='[objectMap]'||type==='[objectSet]'){//使用collectionHandlersproxy=newProxy(target,collectionHandlers)//将代理对象设置为全局Map,我们没有实现proxyMap.set(target,proxy)}returnproxy}constcollectionHandlers={get(target,key){if(key==='__v_raw')returntarget//访问特殊属性,返回原始对象//如果是Set/Map的native方法,返回自己封装的方法//否则返回对象上的属性returnReflect.get(instrumentations.hasOwnProperty(key)?instrumentations:target,key)},}//重写Set/Map的所有原生方法和属性constinstrumentations={get,set,add,has,delete:deleteEntry,clear,forEach,getsize(){returnsize(this)},Instrumentations中的方法重写后的代码就不展示了,下面简单总结一下,有兴趣的可以自行查看源码。所有方法都是通过toRaw(this)获取原始对象,并尝试在其上调用。方法并为所有传入参数解析代理rawKey=toRaw(key),以确保存储在Set/Map中的所有对象都是原始对象。当执行getforEach方法获取数据时,会再次使用reactive包在gethasforEachsize函数中跟踪依赖关系。trigger会在setdeleteclearadd函数中触发。Vue还重写了迭代器的属性/方法(['keys','values','entries',Symbol.iterator]),保证迭代器产生的值全部被reactive包装记录。最后,Vue对Set/Map进行代理的结果是:实际存储的对象是去代理后的原始对象,但是如果你想从里面取出对象,它会被自动代理然后返回。事实上,许多重写的方法都做了双手准备,并尝试同时执行代理和非代理参数。这是为了避免错误。小可爱先用Set保存代理对象,然后传给Vue。结语如果喜欢或者觉得有帮助,希望大家能够点赞关注,给作者鼓励。文中如有不准确之处或疑问,欢迎评论指出。