文章参考了霍春阳的《Vue.js设计与实现》,是自己在阅读过程中的一些思考和理解
分支切换的定义:
随着条件的不同,代买走不同的路径,类似于三元表达式和if语句。
目前的问题:
分支切换会产生遗留的副作用函数。例子:
const data = {
ok: true,
text: 'mean'
}
这样的数据,根据上篇文章的响应式代码,会生成各自的副作用函数,被各自的依赖集合收集。
data
- ok
- effectFn
- text
- effectFn
但是当我将ok的值改为false,并触发副作用函数执行后,text的值不会被读取,所以理论上,text的副作用函数就不应该存在了。然而,按照之前的代码,text的副作用函数仍然存在于text的依赖集合中,也就是产生了副作用函数的遗留,从而导致了不必要的更新。这里的不必要更新指的是,如果我们在此时修改text的值,仍然会执行其对应的副作用函数,但是页面根本不会展示text,这样的函数执行是没有意义的。
解决办法:在每次的副作用函数执行时,可以先把它从所有它存在的依赖集合中删除,因为副作用函数执行后,会建立新的联系。代码如下
function effect(fn) {
const effectfn = () => {
//执行时,将其改名为activeEffect
activeEffect = effectfn
fn()
}
//用于存储与该副作用函数相关的依赖集合
effectfn.deps = []
//执行
effectfn()
}
收集过程(track函数):
get(target, proxy) {
if(!activeEffect) return
//从桶中获取当前对象的Map,因为bocket中可以存储对个对象
let depsMap = bocket.get(target)
//如果没有则立即创建一个,并放在bocket中
//这里映射为 target--->Map<key,Set()>
if(!depsMap) {
bocket.set(target, (depsMap = new Map()))
}
//从对象的众多key中拿到我们要修改的key的Set()集合
//如果没有则立即创建,并加入到depsMap中
//这里的映射为key--->Set(fn,fn,fn....)
let deps = depsMap.get(proxy)
if(!deps) {
depsMap.set(proxy, (deps = new Set()))
}
//将我们传入的修改函数加入到对应key的Set()集合中
deps.add(activeEffect)
⭐️ // 此时deps就是存储当前副作用函数的集合,将其存放在此副作用函数的集合中
activeEffect.deps.push(deps)
//返回读取的值
return target[proxy]
},
当我们在执行track函数时,就会将当前的副作用函数添加到依赖集合deps中,这说明deps就是一个与当前副作用函数存在联系的集合,那就把它存在activeEffect的deps中,从而完成收集。
这样,我们就可以在每次执行副作用函数时,根据effectfn.deps获取的所有相关联的依赖集合,进而将副作用函数从依赖集合中删除。代码如下:
function effect(fn) {
const effectfn = () => {
clearup(effectfn)
activeEffect = effectfn
fn()
}
effectfn.deps = []
effectfn()
}
function clearup(effectfn) {
for (let i=0; i<effectfn.length; i++) {
const deps = effectfn.deps[i]
deps.delete(effectfn)
}
effectfn.deps.length = 0
}
这样虽然可以达到清除副作用函数的作用,但是会导致无限循环。原因是在trigger函数中,我们会执行当前key的副作用函数集合中的副作用函数,执行时,会调用clearup函数清空当前副作用函数的deps集合,并且会再次执行副作用函数:
const effectfn = () => {
clearup(effectfn) //<----这个会执行,然后清空
activeEffect = effectfn
fn() // <---这个会执行,然后会读取值,进而调用get操作,然后有添加新的副作用函数进入到其副作用函数集合中,然后又会继续执行,进而无限循环。
}
避免手段中trigger函数中添加新的集合代替原effect集合进行遍历:
set(target, proxy, value, reciver) {
//原对象中修改要变的值
target[proxy] = value
//获取到target的Map,没有则返回
const depsMap = bocket.get(target)
if(!depsMap) return
//拿到要修改的key的Set集合()
const effects = depsMap.get(proxy)
//依次执行集合中的函数
const effectsToRun = new Set(effects) //<---新的集合
effectsToRun.forEach(effect => effect()) //<--代替遍历
// effects && effects.forEach(fn => fn())
return true
}
这里的简单实现,会将删除的effectfn在执行trigger的时候再次放入到其Key的Set集合中,而源码中是会有一个相等判断。