Vue.js设计与实现分支切换与清除学习总结
作者:玛拉_以琳
这篇文章主要为大家介绍了Vue.js设计与实现分支切换与清除学习总结,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
分支切换
const data = { ok: true, text: 'hello world' } const obj = new Proxy(data, {/*....*/} effect(function effectFn () { document.body.innerText = obj.ok ? obj.text : 'not' })
如上代码, effectFn
函数中存在三元表达式, 根据obj
的ok
属性的值执行响应的代码, ok
属性改变时, 执行的代码也会改变, 就是所谓分支切换. 在effectFn
中, 若ok
的值为true
, 会触发ok
与text
的读取操作, 因此该函数与这两个响应数据都建立了联系, 如下图:
但是当ok
为false
时, 不会对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()) }
目前为止响应式完整代码
// 储存副作用函数的桶 const bucket = new WeakMap() // 用于储存被注册的副作用的函数 let activeEffect = undefined function cleanup (effectFn) { for (let itme of effectFn.deps) { itme.delete(effectFn) } effectFn.deps.length = [] } function effect (fn) { const effectFn = () => { cleanup(effectFn) activeEffect = effectFn fn() } effectFn.deps = [] effectFn() } const data = { text: 'hello world', ok: true } const obj = new Proxy(data, { // 拦截读取操作 get (target, key) { track(target, key) // 返回属性值 return target[key] }, // 拦截设置操作 set (target, key, newVal) { // 设置属性值 target[key] = newVal trigger(target, key) } }) function track (target, key) { // 没有 activeEffect, 直接 return if (!activeEffect) return target[key] // 根据 target 从'桶'中回去 depsMap, 它也是一个 Map 类型: key ---> effects let depsMap = bucket.get(target) // 如果 depsMap 不存在, 则新建一个 Map 并与 target 关联 if (!depsMap) bucket.set(target, (depsMap = new Map())) // 再根据 key 从depsMap 中去的 deps, 它是一个 Set 类型 // 里面存贮所有与当前 key 相关的副作用函数: effects let deps = depsMap.get(key) // 如果 deps 不存在, 同样新建一个 Set 并与 key 关联0 if (!deps) depsMap.set(key, (deps = new Set())) // 最后将当前激活的副作用函数添加到'桶'里 deps.add(activeEffect) } function trigger (target, key) { // 根据 target 从'桶'中取得 depsMap, 它是 key --> effects const depsMap = bucket.get(target) if (!depsMap) return // 根据 key 取得所有的副作用函数 effects const effects = depsMap.get(key) // 执行副作用函数 // effects && effects.forEach(fn => fn()) const effectsFnRun = new Set(effects) effectsFnRun.forEach(fn => fn()) } effect(() => { console.log('effect run'); document.body.innerText = obj.ok ? obj.text : 'not' }) setTimeout(() => { obj.ok = false }, 2000)
以上就是Vue.js设计与实现分支切换与清除学习总结的详细内容,更多关于Vue.js分支切换清除的资料请关注脚本之家其它相关文章!