持续创作,加速成长!这是我参与「掘金日新计划 · 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新增一个属性,能得到响应吗?
所以这里,还需要再次处理,我们需要提取一个关系,一个副作用函数与被操作的目标字段之间的联系。
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