聊聊vue实现原理 避免无限递归循环

329 阅读2分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。

避免无限递归循环

接着上回说到,vue的响应式原理,我们通过在Proxy的getter拦截属性收集依赖,setter中触发依赖,从而可以实现页面的实时更新 响应式原理基础版请戳 效果图:

chrome-capture-2022-4-15.gif

大概代码如下:

 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++
    })

问题

当我们在页面执行时,发现栈溢出了!

image.png 什么鬼? image.png

分析

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++
})