携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第14天,点击查看活动详情
5.7.3 数组的查找方法
在数组的API中,有一些是查找的方法,这一类方法有个共同特点就是不会改变原数据,本质上都是一个循环遍历。 前文又讲,只要在副作用函数与数组长度和索引之间建立响应式联系,就能够响应数组,但是具体到某些方法,我们还是需要特殊处理,就是所谓的边界条件。
我们看这样一个例子.
const obj = {}
const arr = reactive([obj])
console.log(arr.includes(arr[0])) // false
毫无疑问,这里应该返回true的,但是结果却相反。如果你把这本书到目前为止都仔细看了,你大概能一眼猜到什么问题。
由于我们的reactive是会递归的,因此我们的arr内部的元素也是经过reactive处理过的,这时候arr[0]的指向就会改变了,不再是obj而是reactive(obj)。而includes本质上也是一个循环,每次调用reactive函数都会获得一个新的reactive对象,从而导致每次对比的对象都不是同一个,那么自永远是false
解决的办法很简单,我们把原始对象和对应的reactive对象存储起来,如果存在我们就不再重新生成reactive对象,这样即避免了错误,又能提高效率。(PS. 为什么每次我都没到问题,作者每次都在下一章提出上一章的bug)
const ReactiveObjectMap = new Map()
const reactive = (obj)=>{
if(ReactiveObjectMap.has(obj)){
return ReactiveObjectMap.get(obj)
}
/*省略其他逻辑*/
}
通过一个Map,我们避免了重复去创建代理的过程,这样能够保证一个对象我们只代理一次,从而解决了includesb的问题。
但是,作者告诉我们高兴的太早了,还有一个bug.我们把之前那个语句小改一下,诶,又返回false了。我们在includes不传入响应式对象,而是传入原始对象,console.log(arr.includes(obj)) // false理论上这时候也应该是true,你会发现又变成了false.
这个原因也很好解释,因为includes内部对比的对象是一个响应对象而不是原始对象了。要解决这个问题就比较麻烦了,我们需要重写这个方法。(怎么感觉又是vue2的老思路了)
但与vue2不同的时候,我们可以不需要修改数组原型上的相应方法,而是通过代理拦截[[Get]]方法,直接去执行新的函数就可以了
const arrayMethods = {
includes(...args){
let res = Array.prototype[key].apply(this,args)
if(!res){
res = Array.prototype[key].apply(this.raw,args)
}
return res
}
}
const createReactive = (obj,deepify=false,readonly=false)={
return new Proxy(obj,{
get(target,key,receiver){
/*省略其他逻辑*/
if(Array.isArray(target) && key in arrayMethods){
return Reflect.get(arrayMethods,key,receiver)
}
}
})
}
基本实现就是这样,我觉得这样会比vue2的时候更好,因为我们只是在代理对象中重定向了一些内置方法,而不是重写,当你使用的不是代理对象时,内置方法依旧不变。