本文已参与「新人创作礼」活动,一起开启掘金创作之路。
避免无限递归循环
接着上回说到,vue的响应式原理,我们通过在Proxy的getter拦截属性收集依赖,setter中触发依赖,从而可以实现页面的实时更新 响应式原理基础版请戳 效果图:
大概代码如下:
let obj={
flag:true,
count:1,
name:"zhangsan"
}
let effect=(fn)=>{
...
}
let newObj=new Proxy(obj,{
get(target,key){
track(target,key)
return Reflect.get(target,key)
},
set(target,key,value){
Reflect.set(target,key,value)
trigger(target,key)
return true
},
})
//分支切换
function cleanUp(effectFn){
...
}
function track(target,key){
...
}
function trigger(target,key){
...
}
effect(()=>{
console.log("newObj.name的值是"+newObj.name)
}
})
newObj.name="lisi"
一般情况下,我们都会在effect传入的函数中去读取一个属性,然后在修改它。
如果,我们在effect中即读取,又设置值呢?会发生什么? 比如这样:
effect(()=>{
newObj.count++
})
问题
当我们在页面执行时,发现栈溢出了!
什么鬼?
分析
newObj.count++ 等价于newObj.count= newobj.count + 1 首先,读取count的值,会执行effect()
然后我们在执行过程中,发现里面竟然有一个修改count的表达式,trigger:这不得马上执行? 立马执行!
effect() effect() effect()...
完了,我进入循环了!
解决
我们思考一下怎么解决?
能从track入手吗? 不能,人家是收集依赖的,还能拦着不让收集吗?
只能从trigger入手
拦着不让你触发:增加守卫条件 当前正在执行的函数和trigger触发的函数一样,就不执行
真假美猴王只能有一个!
function trigger(target,key){
let depMaps=bucket.get(target)
let deps=depMaps.get(key)
let effectTorun=new Set(deps)
effectTorun.forEach(effectFn => {
增加防止无限递归循环的代码
if(effectFn!==activeEffect){
effectFn()
}
});
}
完整代码如下:
/**
*
* 1.解决无限递归执行的问题
* 2.增加调度执行
*/
let obj={
flag:true,
count:1,
name:"zhangsan"
}
let activeEffect=null
let effectStack=[]//
let effect=(fn)=>{
//清空effectFn 的deps
const effectFn=()=>{
cleanUp(effectFn)
activeEffect=effectFn
effectStack.push(effectFn)
fn()
effectStack.pop()
activeEffect=effectStack[effectStack.length-1]
}
effectFn.deps=[]
effectFn()
}
let bucket=new WeakMap()
let newObj=new Proxy(obj,{
get(target,key){
track(target,key)
return Reflect.get(target,key)
},
set(target,key,value){
Reflect.set(target,key,value)
trigger(target,key)
return true
},
})
//分支切换
function cleanUp(effectFn){
for (let i = 0; i < effectFn.deps.length; i++) {
const deps=effectFn.deps[i]
deps.delete(effectFn)
}
effectFn.deps.length=0
}
function track(target,key){
let depMaps=bucket.get(target)
if(!depMaps){
bucket.set(target,(depMaps=new Map()))
}
let deps=depMaps.get(key)
if(!deps){
depMaps.set(key,(deps=new Set()))
}
deps.add(activeEffect)
activeEffect.deps.push(deps)
}
function trigger(target,key){
let depMaps=bucket.get(target)
let deps=depMaps.get(key)
let effectTorun=new Set(deps)
effectTorun.forEach(effectFn => {
增加防止无限递归循环的代码
if(effectFn!==activeEffect){
effectFn()
}
});
}
/**
* 解决无限递归执行,如:newObj.count++ ,读取之后,然后修改。这个执行动作很快,导致栈溢出
* 先读取,然后收集到bucket,然后修改,触发依赖,这个副作用函数还在执行中,还没执行完毕,就要进行下一次执行
* 可以这样理解:effect() 读取执行, effect() 修改执行
* 我们可以在trigger的时候,去看一下,如果触发执行的副作用函数和当前执行的副作用函数一样,就不去执行它
*/
effect(()=>{
newObj.count++
})