阅读 《vue.js 设计与实现》4.4

2022-08-09  本文已影响0人  alue

存在问题 - 分支切换

有时候,虽然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())
}

总结

我们可以看到闭包的强大之处,可以随心所欲为已有数据结构增加一个属性,同时对外接口保持不变。

上一篇下一篇

猜你喜欢

热点阅读