持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第28天,点击查看活动详情
4.6 避免无限递归循环
在我们之前写的那篇代码中,有一个在很特殊的情况下的bug,隐藏极深,本篇文章就一起来看看这个bug。
先来看bug的代码
const data = reactive({num:0})
effect(()=>{
data.num++ // data.num = data.num + 1
})
在上面那个代码中,对于同一个值进行读取和赋值操作,也就是说触发track的时候,同时又触发了trigger。我们可以想象一下effect的函数,刚刚执行了data.num + 1,这个操作可能还没执行完,又需要track,然后又执行,这样就好比函数值执行过程中不断再次调用自己,这就形成了循环调用,最后的结果就是堆栈溢出。
既然找到了问题,那我们就好解决了。我们需要在trigger的时候,注意一下不要触发同一个,或者正在运行中的副作用函数就可以了。
为什么不在track的时候判断呢,书中并没写。但我觉得,我们很难去判断哪些操作是无需track的,这一部分的代码在运行时中很难操作。要不是就是在编译时操作,之前也说过,Vue是一个运行时+编译时的框架,这部分理论上可以放到编译时去优化,但是目前来看,Vue团队似乎并不太想去优化这部分编译时,他们把这部分放到了运行时来优化。
解决的办法也很简单,是需要!==去判断一下。
const trigger = (target,key)=>{
let depsMap = targetsMap.get(target);
if (!depsMap) { return; }
let deps = depsMap.get(key);
const effectToRun = new Set()
deps && deps.forEach(fn=>{
if(fn !== activeEffect){
effectToRun.add(fn)
}
})
effectToRun.forEach(fn=>fn())
}
我们新增了一个effectToRun数组,在遍历deps的时候,我们判断一下当前执行的副作用函数与deps中是否相同,如果不相同再把它放到effectToRun中,最后再遍历并执行effectToRun。
为什么!==可以判断两个函数是否相同呢?
其实对于引用类型来说,当前变量存储的其实是内存重点地址,又叫指针。这个指针指向一个堆的地址,因此当两个地址相等的时候,必然指向同一个函数。