vue响应系统的作用和实现三

切换分支和cleanup

        如果在副作用函数中传入的fn是一个三元函数,比如document.body.innertext = obj.ok ? obj.text : 'not'。在这种情况下,obj.ok是true还是false就决定了分支的走向。

        如果obj.ok是true,那么在代理的数据中就会涉及到obj.ok 和 obj.text两个属性的读取操作,就会有两个deps收集到fn这个副作用函数。如果是false,由于'not'是固定的值,不会影响其他变量,所以应该是在proxy代理中只有obj.ok这个属性涉及到读取操作,就只有一个deps收集到fn这个副作用函数。

        但是在vue响应系统的作用和实现二的代理中,即使是obj.ok的值为false,修改obj.text还是会执行副作用函数fn。其他代码参考vue响应系统的作用和实现二最后的代码。

//调用effect
effect(
    ()=>{
        //会执行三次,第一次是effect中的fn(),第二次obj.ok的set操作,第三次是obj.text的set操作
        console.log('effect run')
        document.body.innerText = obj.ok ? obj.text : 'not'
    }
)

setTimeout(()=>{
    obj.ok = false
    obj.text = 'hello,vue!'
},1000)

        解决这个问题的思路是每次副作用函数执行时,我们先把它从所有与之关联的依赖集合中删除,在进行依赖添加。

let activeEffect = null
function effect(fn){
    const effectFn = () =>{     //新增
        //调用cleanup函数完成清除工作
        cleanup(effectFn)   //新增
        //当effectFn执行时,将其设置为当前激活的副作用函数
        activeEffect = effectFn   //修改
        fn()
    }
    //effectFn.deps用来存储所有与该副作用函数相关联的依赖集合
    effectFn.deps = []  //新增
    effectFn()  //新增
}

const data = { ok:true, text:'hello,world' }
const bucket = new WeakMap()  
const obj = new Proxy(data,{
    get(target,key){
        track(target,key)
        return Reflect.get(target, key) 
    },
    set(target,key,newVal){
        target[key] = newVal
        trigger(target,key)
    }
})

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就是一个与该副作用函数相关联的依赖集合
    //相当于哪个deps添加了activeEffect副作用函数,就把他收集起来,到时候清除的时候统一循环清除
    activeEffect.deps.push(deps)  //新增
}

function trigger(target,key){
    const depsMap = bucket.get(target)
    if(!depsMap) return
    const effects = depsMap.get(key)
    //由于set.prototype.forEach的规范问题,为了避免无限执行
    const effectsToRun = new Set(effects)   //新增
    effectsToRun && effectsToRun.forEach(fn=>fn())  //新增
}

//清除集合依赖函数
function cleanup(effectFn) {
    for(let i= 0; i<effectFn.deps.length; i++){
        //deps就是track函数中那个depsMap.get(key),依赖集合
        const deps = effectFn.deps[i]
        //将effectFn从依赖集合中移除,实际上就是deps.add(activeEffect)的反义
        deps.delete(effectFn)
    }
    //重置数组长度
    effectFn.deps.length = 0
}

effect(
    ()=>{
        //会执行两次
        console.log('effect run')
        document.body.innerText = obj.ok ? obj.text : 'not'
    }
)

setTimeout(()=>{
    obj.ok = false
    obj.text = 'hello,vue!'
},1000)

其中,语言规范问题,Set.prototype.forEach会无限执行,所以添加新的Set来进行循环。

感兴趣的小伙伴可以查看一下ECMA2020中的695-696页。

以上参考霍春阳老师的《Vue.js设计与实现》,有兴趣的朋友可以看一下原书,更加详细·-·

  • 9
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值