存在问题 - 分支切换
有时候,虽然effect读取了data的某个属性,但实际上,这个读取是没有副作用的。例如,
something = obj.ok ? obj.text:'not'
如果obj.ok
是false的话,obj.text
并不会有副作用,但之前的实现方式会冗余执行。
解决方法:
每次副作用执行时,先将effect从桶中删除,然后再重新建立联系。
桶的数据结构需要优化
目的:为了快速找到effect在桶中的所有位置。
思路:给effect添加一个数组,能够存储所有包含自己的集合。
let activeEffect
function effect(fn) {
activeEffect = fn
fn()
}
修改为
let activeEffect
function effect(fn) {
const effectFn = () => {
activeEffect = effectFn
fn()
}
effectFn.deps = [] // 增加的属性
effectFn()
}
同时,track函数末尾增加一行
function track(target, key) {
// 末尾增加
activeEffect.deps.push(deps)
}
也就是说,每次A注册effect的同时,effect自身也注册了A。
这样我们能够定义清理函数
function cleanup(effectFn) {
for (let i = 0; i < effectFn.deps.length; i++) {
const deps = effectFn.deps[i];
deps.delete(effectFn);
}
effectFn.deps.length = 0
}
只要在执行副作用前,先清除桶里旧的effect即可。
无限循环的BUG
由于trigger函数会依次执行依赖集合中的全部effect。而执行effect的第一步,就是将自身先从集合中删除,然后执行副作用行为,又把自己加入了那个集合。这样造成了无限循环。
解决方式
复制一份,然后运行复制的集合即可避免死循环。
function trigger(target, key) {
const depsMap = bucket.get(target)
if (!depsMap) return
const effects = depsMap.get(key);
// 下面这句话会导致无限循环
// effects && effects.forEach(fn => fn())
// 复制避免死循环
const effectsToRun = new Set(effects)
effectsToRun.forEach(effectFn => effectFn())
}
总结
我们可以看到闭包的强大之处,可以随心所欲为已有数据结构增加一个属性,同时对外接口保持不变。
网友评论