vue.js設計與實現(xiàn)(四)-響應系統(tǒng)-分支切換與cleanup

問題描述:在上一篇中,通過weakmap,map,Set數(shù)據(jù)結構,建立了代理對象中 target ,key, 副作用函數(shù)之前的對應關系,使得我們可以處理不存在的屬性,避免不必要的程序執(zhí)行,但上一篇的完善結構中還有一個問題出現(xiàn),測試如下程序:

const data = {ok:true,text:'hello world'}
    let bucket = new WeakMap()
    let activeEffect;
    function effect(fn){
        activeEffect = fn;
        fn()
    }
    function track(target,key){
        if(!activeEffect) return
        let depsMap = bucket.get(target)
        if(!depsMap){bucket.set(target,depsMap = new Map())}
        let deps = depsMap.get(key)
        if(!deps){depsMap.set(key,deps = new Set())}
        deps.add(activeEffect)
    }

    function trigger(target,key){
        let depsMap = bucket.get(target)
        if(!depsMap) return
        const effects = depsMap.get(key)
        effects && effects.forEach(fn=>fn())
    }
    const obj = new Proxy(data,{
        get(target,key){
            track(target,key)
            return target[key]
        },
        set(target,key,newVal){
            target[key] = newVal;
            trigger(target,key)
        }
    })
    effect(
        function effectFn(){
            console.log('effect')
            document.body.innerText = obj.ok ? obj.text :'not' 
        }
    )
    setTimeout(()=>{
        obj.ok = false
        obj.text = 'hello vue3'
    })

完善結構后我們可以看到打印如下:


image.png

理論上在副作用函數(shù)中的判斷后,我無論再去怎么改變obj.text的值,副作用函數(shù)都不應該再去執(zhí)行一次,所以本次代碼就是要解決如上問題;

解決思路很簡單,在副作用函數(shù)執(zhí)行前,刪除所有與復函數(shù)關聯(lián)的依賴屬性即可

function effect(fn){
        const effectFn = ()=>{
            cleanup(effectFn)
            activeEffect = effectFn;
            fn()
        }
        // activeEffect.deps用來存儲所有與該副作用函數(shù)相關聯(lián)的依賴集合
        effectFn.deps=[]
        // 執(zhí)行副作用函數(shù)
        effectFn()
    }

 function cleanup(effectFn){
        for(let i=0;i<effectFn.deps.length;i++){
            effectFn.deps[i].delete(effectFn)
        }
        // 重置數(shù)組
        effectFn.deps.length = 0
    }

track函數(shù)添加deps函數(shù)集合
    function track(target,key){
        if(!activeEffect) return
        let depsMap = bucket.get(target)
        if(!depsMap){bucket.set(target,depsMap = new Map())}
        let deps = depsMap.get(key)
        if(!deps){depsMap.set(key,deps = new Set())}
        deps.add(activeEffect)
        // 添加到deps數(shù)組中
        activeEffect.deps.push(deps)
    }

寫到這里可以避免復函數(shù)的遺留了,但我們運行程序后會發(fā)現(xiàn)無線循環(huán)


image.png

問題出現(xiàn)在這里

01 function trigger(target, key) {
02   const depsMap = bucket.get(target)
03   if (!depsMap) return
04   const effects = depsMap.get(key)
05   effects && effects.forEach(fn => fn()) // 問題出在這句代碼
06 }

解釋:
在trigger函數(shù)中,effects存儲在Set數(shù)據(jù)結構中,在遍歷執(zhí)行復函數(shù)時,cleanup會先刪除當前復函數(shù),然后在注冊復函數(shù)的時候又把當前復函數(shù)添加到Set集合中,而此時Set集合還在遍歷,就會導致無線循環(huán),舉例解釋如下代碼:

01 const set = new Set([1])
02
03 set.forEach(item => {
04   set.delete(1)
05   set.add(1)
06   console.log('遍歷中')
07 })

語言規(guī)范中對此有明確的說明:在調(diào)用 forEach 遍歷 Set 集合時,如果一個值已經(jīng)被訪問過了,但該值被刪除并重新添加到集合,如果此時 forEach 遍歷沒有結束,那么該值會重新被訪問。因此,上面的代碼會無限執(zhí)行。解決辦法很簡單,我們可以構造另外一個 Set 集合并遍歷它:

01 const set = new Set([1])
02
03 const newSet = new Set(set)
04 newSet.forEach(item => {
05   set.delete(1)
06   set.add(1)
07   console.log('遍歷中')
08 })

回到 trigger 函數(shù),我們需要同樣的手段來避免無限執(zhí)行:

01 function trigger(target, key) {
02   const depsMap = bucket.get(target)
03   if (!depsMap) return
04   const effects = depsMap.get(key)
05
06   const effectsToRun = new Set(effects)  // 新增
07   effectsToRun.forEach(effectFn => effectFn())  // 新增
08   // effects && effects.forEach(effectFn => effectFn()) // 刪除
09 }

整體代碼構建如下:

const data = {ok:true,text:'hello world'}
    let bucket = new WeakMap()
    let activeEffect;
    function effect(fn){
        const effectFn = ()=>{
            cleanup(effectFn)
            activeEffect = effectFn;
            fn()
        }
        // activeEffect.deps用來存儲所有與該副作用函數(shù)相關聯(lián)的依賴集合
        effectFn.deps=[]
        // 執(zhí)行副作用函數(shù)
        effectFn()
    }
    function cleanup(effectFn){
        for(let i=0;i<effectFn.deps.length;i++){
            effectFn.deps[i].delete(effectFn)
        }
        // 重置數(shù)組
        effectFn.deps.length = 0
    }
    function track(target,key){
        if(!activeEffect) return
        let depsMap = bucket.get(target)
        if(!depsMap){bucket.set(target,depsMap = new Map())}
        let deps = depsMap.get(key)
        if(!deps){depsMap.set(key,deps = new Set())}
        deps.add(activeEffect)
        activeEffect.deps.push(deps)
    }

    function trigger(target,key){
        let depsMap = bucket.get(target)
        if(!depsMap) return
        const effects = depsMap.get(key)
        const effectsToRun = new Set(effects)
        // effects && effects.forEach(fn=>fn())
        effectsToRun.forEach(effectFn=>effectFn())
    }
    const obj = new Proxy(data,{
        get(target,key){
            track(target,key)
            return target[key]
        },
        set(target,key,newVal){
            target[key] = newVal;
            trigger(target,key)
        }
    })
    effect(
        function effectFn(){
            console.log('effect')
            document.body.innerText = obj.ok ? obj.text :'not' 
        }
    )
    setTimeout(()=>{
        obj.ok = false
        obj.text = 'hello vue3'
    })
    

結果查看:


image.png
最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容