关于前端:Vuejs设计与实现学习总结第四章2分支切换与清除


highlight: monokai-sublime

分支切换

const data = { ok: true, text: 'hello world' }
const obj = new Proxy(data, {/*....*/}

effect(function effectFn () {
    document.body.innerText = obj.ok ? obj.text : 'not'
})

如上代码, effectFn函数中存在三元表达式, 依据objok属性的值执行响应的代码, ok属性扭转时, 执行的代码也会扭转, 就是所谓分支切换. 在effectFn中, 若ok的值为true, 会触发oktext的读取操作, 因而该函数与这两个响应数据都建设了分割, 如下图:

然而当okfalse时, 不会对text进行读取操作, 因而无论text的值怎么扭转, DOM都不会进行更新, 然而因为与辅助用函数进行了绑定, 函数仍然会运行, 这是不应该的.

为要解决这个问题, 能够在每次副作用函数执行时, 能够先将其从所有与之关联的汇合中删除. 因而就须要记录哪些汇合中(就是上图中的Set)含有该副作用函数, 能够给副作用函数, 增加一个属性, 值为数组用于记录, 如下代码:

// 应用全局变量贮存被注册的副作用函数
let activeEffect = undefined
function effect (fn) {
    const effectFn = () => {
        // 当 effectFn 执行时, 将其设置为以后激活的副作用函数
        activeEffect = effectFn
        fn()
    }
    
    // activeEffect.deps 用与贮存所有蕴含该副作用函数(即关联该副作用函数)的汇合 Set
    activeEffect.deps = []
    // 执行副作用函数
    effectFn()
 }

在副作用函数是Proxy中的拦挡读取操作时绑定的, 因而能够在拦挡读取操纵中收集蕴含该副作用函数的汇合:

function track (target, key) {
    // 没有 activeEffect 间接完结
    if (activeEffect) return
    // 依据 target 从 WeakMap中获取 Map
    let depsMap = bucket.get(target)
    if (!depsMap) bucket.set(target, (depsMap = new Map()))
    
    let desp = depsMap.get(key)
    if (!deps) depsMap.set(key, (deps = new Set()))
    // 将以后激活的副作用函数增加到依赖汇合中
    deps.add(activeEffect)
    
    // deps 就是存有以后副作用函数的汇合, 即存在分割的依赖汇合
    // 将其增加到 activeEffect.deps 中
    activeEffect.deps.push(deps)
}

关系如下图:

革除依赖

依据上图的分割, 就能够在副作用函数每次执行时, 依据 effectFn.deps 将副作用函数从依赖中(Set)删除

// 应用全局变量贮存被注册的副作用函数
let activeEffect = undefined
function effect (fn) {
    const effectFn = () => {
        // 调用 cleanup 函数实现清理
        cleanup(effectFn)
        // 当 effectFn 执行时, 将其设置为以后激活的副作用函数
        activeEffect = effectFn
        fn()
    }
    
    // activeEffect.deps 用与贮存所有蕴含该副作用函数(即关联该副作用函数)的汇合 Set
    activeEffect.deps = []
    // 执行副作用函数
    effectFn()
 }
 
 function cleanup(effectFn) {
     // 遍历数组
     for (let item of effectFn.deps){
         // item 就是副作用函数汇合(Set)
         item.delete(effectFn)
     }
     
     // 最初重置数组
     effectFn.deps.length = 0
 }

但此时会导致当初的响应式代码呈现有限循环, 问题出在拦挡设置操作中:

function trigger (target, key) {
    const depsMap = bucket.get(target)
    if (!depsMap) return
    const effects = depsMap.get(key)
    effects && effects.forEach(fn => fn()) // 问题出在这一行
}

下面代码中, 最初一行遍历的effects实际上就是以后key的副作用汇合Set, 在遍历中副作用函数会运行, 此时会cleanup进行革除, 然而副作用函数的执行又会将其从新被收集到同一个汇合中, 呈现了一边删除该函数一边收集该函数导致死循环. 能够用另一个SSet进行遍历

function trigger (target, key) {
    const depsMap = bucket.get(target)
    if (!depsMap) return
    const effects = depsMap.get(key)
    
    const effectsToRun = new Set(effects)
    effectsToRun.forEach(fn => fn())
}

评论

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

这个站点使用 Akismet 来减少垃圾评论。了解你的评论数据如何被处理