精读《Vuejs设计与实现》(14)之响应系统4

114 阅读3分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第23天,点击查看活动详情

4.3设计一个完善的响应式系统

在上一篇中,书里简单实现了一个响应式系统,但是很明显那个是有问题的,比如如果effect这个函数名变了,代码就栓Q了,所以这一章节,我们将尝试优化一下

  • 首先是effect这个函数名的问题 解决这个问题很简单,我们提供的一个方法,通过这个方法来收集effect即可,这样无论你的函数是什么名字,哪怕是匿名函数,只需要调用这个方法就可以了,与函数名无关
//这个变量名也出现在了vue3,想一想为什么

let activeEffect
function effect = (fn)=>{
 activeEffect = fn
 fn()
} 

其实这里我一样有个疑问,如果同时有2个effect运行怎么办呢? 其实完全不必要担心这个问题,因为调用有次序,根本不可能同一时间同时调用。

上一篇的文章修改如下

const effects = new Set()
const data = {text:'hello'}
const obj = new Proxy(data,{
    get(target,key){
        if(activeEffect){
            effects.add(effect)
        }
        return target[key]
    },
    set(target,key,value){
     target[key] = value
     effect.forEach(fn=>fn())
     return true
    }
})

使用的时候,可以这样effect(()=>document.body.innerHTML = obj.text),就可以注册一个匿名函数了。

  • 处理新增属性的问题 在vue2中,如果你通过this新增一个属性,有可能响应式失效的。大家都知道是Object.defineProperty的问题,当vue初始变量的时候,没有这个属性,因此这个属性就会没有响应式。

在vue3中,proxy本来不应该存在这个问题,但是你看我们上面的代码,如果你给data新增一个属性,能得到响应吗?

所以这里,还需要再次处理,我们需要提取一个关系,一个副作用函数与被操作的目标字段之间的联系

image.png

const effectStore = new WeakMap()
const data = {text:'hello'}
const obj = new Proxy(data,{
    get(target,key){
        if(!activeEffect){
            return 
        }
        let deps = effectStore.get(key)
        if(effectStore.has(key)){
            deps = effectStore.get(key)
            deps.add(activeEffect)
        } else {
            deps = new Set()
            deps.add(activeEffect)
            effectStore.set(key,deps)
        }
        return target[key]
    },
    set(target,key,value){
     target[key] = value
     const deps =  effectStore.get(key)
     deps && deps.forEach(fn=>fn())
     return true
    }
})

我们这里用了weakMap去加强内存回收,建立了一个以对象Key作为键,副作用Effect的Set为值的一个map,也就是对应关系,这样我们可以按key去存储相应的副作用的函数。

为什么要选择WeakMap呢?因为WeakMap属于弱引用,当你删除一个key,那么就会被垃圾回收,对应的键和值就都范访问不到了。因为我们这个依赖关系其实是以key的存在而存在的,一旦key消失,我们没必要保存这个依赖关系。

比如上文代码中data.text不存在了,那么document.body.innerHTML = data.text,这个语句就没有必要存在了,如果执行了,反而显示一个undefined,其实是错误的。因为我们需要使用弱引用,具体的文档可以看这里 阮一峰ES6-WeakMap