Vue2响应式系统之分支切换怎么实现
本篇内容介绍了"Vue2响应式系统之分支切换怎么实现"的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!
场景
我们考虑一下下边的代码会输出什么。
import { observe } from "./reactive";import Watcher from "./watcher";const data = { text: "hello, world", ok: true,};observe(data);const updateComponent = () => { console.log("收到", data.ok ? data.text : "not");};new Watcher(updateComponent); // updateComponent 执行一次函数,输出 hello, worlddata.ok = false; // updateComponent 执行一次函数,输出 notdata.text = "hello, liang"; // updateComponent 会执行吗?我们来一步一步理清:
observer(data)
拦截了 中 和 的 ,并且各自初始化了一个 实例,用来保存依赖它们的 对象。datatextokget、setDepWatcher

new Watcher(updateComponent)
这一步会执行 函数,执行过程中用到的所有对象属性,会将 收集到相应对象属性中的 中。updateComponentWatcherDep

当然这里的 其实是同一个,所以用了指向的箭头。Watcher
data.ok = false
这一步会触发 ,从而执行 中所有的 ,此时就会执行一次 。setDepWatcherupdateComponent
执行 就会重新读取 中的属性,触发 ,然后继续收集 。updateComponentdatagetWatcher
重新执行 函数 的时候:updateComponent
const updateComponent = () => { console.log("收到", data.ok ? data.text : "not");};因为 的值变为 ,所以就不会触发 的 , 的 就不会变化了。data.okfalsedata.textgettextDep
而 会继续执行,触发 收集 ,但由于我们 中使用的是数组,此时收集到的两个 其实是同一个,这里是有问题,会导致 重复执行,一会儿我们来解决下。data.okgetWatcherDepWacherupdateComponent
data.text = "hello, liang"
执行这句的时候,会触发 的 ,所以会执行一次 。但从代码来看 函数中由于 为 , 对输出没有任何影响,这次执行其实是没有必要的。textsetupdateComponentupdateComponentdata.okfalsedata.text
之所以执行了,是因为第一次执行 读取了 从而收集了 ,第二次执行 的时候, 虽然没有读到,但之前的 也没有清除掉,所以这一次改变 的时候 依旧会执行。updateComponentdata.textWatcherupdateComponentdata.textWatcherdata.textupdateComponent
所以我们需要的就是当重新执行 的时候,如果 已经不依赖于某个 了,我们需要将当前 从该 中移除掉。updateComponentWatcherDepWatcherDep
问题
总结下来我们需要做两件事情。
去重, 中不要重复收集 。
DepWatcher重置,如果该属性对 中的 已经没有影响了(换句话就是, 中的 已经不会读取到该属性了 ),就将该 从该属性的 中删除。
DepWacherWatcherupdateComponentWatcherDep
去重
去重的话有两种方案:
Dep中的 数组换为 。subsSet每个 对象引入 , 对象中记录所有的 的 ,下次重新收集依赖的时候,如果 的 已经存在,就不再收集该 了。
DepidWatcherDepidDepidWatcher
Vue2 源码中采用的是方案 这里我们实现下:2
Dep 类的话只需要引入 即可。id
/*************改动***************************/let uid = 0;/****************************************/export default class Dep { static target; //当前在执行的函数 subs; // 依赖的函数 id; // Dep 对象标识 constructor() { /**************改动**************************/ this.id = uid++; /****************************************/ this.subs = []; // 保存所有需要执行的函数 } addSub(sub) { this.subs.push(sub); } depend() { if (Dep.target) { // 委托给 Dep.target 去调用 addSub Dep.target.addDep(this); } } notify() { for (let i = 0, l = this.subs.length; i < l; i++) { this.subs[i].update(); } }}Dep.target = null; // 静态变量,全局唯一在 中,我们引入 来记录所有的 。Watcherthis.depIdsid
import Dep from "./dep";export default class Watcher { constructor(Fn) { this.getter = Fn; /*************改动***************************/ this.depIds = new Set(); // 拥有 has 函数可以判断是否存在某个 id /****************************************/ this.get(); } /** * Evaluate the getter, and re-collect dependencies. */ get() { Dep.target = this; // 保存包装了当前正在执行的函数的 Watcher let value; try { value = this.getter.call(); } catch (e) { throw e; } finally { this.cleanupDeps(); } return value; } /** * Add a dependency to this directive. */ addDep(dep) { /*************改动***************************/ const id = dep.id; if (!this.depIds.has(id)) { dep.addSub(this); } /****************************************/ } /** * Subscriber interface. * Will be called when a dependency changes. */ update() { this.run(); } /** * Scheduler job interface. * Will be called by the scheduler. */ run() { this.get(); }}重置
同样是两个方案:
全量式移除,保存 所影响的所有 对象,当重新收集 的前,把当前 从记录中的所有 对象中移除。
WatcherDepWatcherWatcherDep增量式移除,重新收集依赖时,用一个新的变量记录所有的 对象,之后再和旧的 对象列表比对,如果新的中没有,旧的中有,就将当前 从该 对象中移除。
DepDepWatcherDep
Vue2 中采用的是方案 ,这里也实现下。2
首先是 类,我们需要提供一个 方法。DepremoveSub
import { remove } from "./util";/*export function remove(arr, item) { if (arr.length) { const index = arr.indexOf(item); if (index > -1) { return arr.splice(index, 1); } }}*/let uid = 0;export default class Dep { static target; //当前在执行的函数 subs; // 依赖的函数 id; // Dep 对象标识 constructor() { this.id = uid++; this.subs = []; // 保存所有需要执行的函数 } addSub(sub) { this.subs.push(sub); } /*************新增************************/ removeSub(sub) { remove(this.subs, sub); } /****************************************/ depend() { if (Dep.target) { // 委托给 Dep.target 去调用 addSub Dep.target.addDep(this); } } notify() { for (let i = 0, l = this.subs.length; i < l; i++) { this.subs[i].update(); } }}Dep.target = null; // 静态变量,全局唯一然后是 类,我们引入 来保存所有的旧 对象,引入 来保存所有的新 对象。Watcherthis.depsDepthis.newDepsDep
import Dep from "./dep";export default class Watcher { constructor(Fn) { this.getter = Fn; this.depIds = new Set(); // 拥有 has 函数可以判断是否存在某个 id /*************新增************************/ this.deps = []; this.newDeps = []; // 记录新一次的依赖 this.newDepIds = new Set(); /****************************************/ this.get(); } /** * Evaluate the getter, and re-collect dependencies. */ get() { Dep.target = this; // 保存包装了当前正在执行的函数的 Watcher let value; try { value = this.getter.call(); } catch (e) { throw e; } finally { /*************新增************************/ this.cleanupDeps(); /****************************************/ } return value; } /** * Add a dependency to this directive. */ addDep(dep) { const id = dep.id; /*************新增************************/ // 新的依赖已经存在的话,同样不需要继续保存 if (!this.newDepIds.has(id)) { this.newDepIds.add(id); this.newDeps.push(dep); if (!this.depIds.has(id)) { dep.addSub(this); } } /****************************************/ } /** * Clean up for dependency collection. */ /*************新增************************/ cleanupDeps() { let i = this.deps.length; // 比对新旧列表,找到旧列表里有,但新列表里没有,来移除相应 Watcher while (i--) { const dep = this.deps[i]; if (!this.newDepIds.has(dep.id)) { dep.removeSub(this); } } // 新的列表赋值给旧的,新的列表清空 let tmp = this.depIds; this.depIds = this.newDepIds; this.newDepIds = tmp; this.newDepIds.clear(); tmp = this.deps; this.deps = this.newDeps; this.newDeps = tmp; this.newDeps.length = 0; } /****************************************/ /** * Subscriber interface. * Will be called when a dependency changes. */ update() { this.run(); } /** * Scheduler job interface. * Will be called by the scheduler. */ run() { this.get(); }}测试
回到开头的代码
import { observe } from "./reactive";import Watcher from "./watcher";const data = { text: "hello, world", ok: true,};observe(data);const updateComponent = () => { console.log("收到", data.ok ? data.text : "not");};new Watcher(updateComponent); // updateComponent 执行一次函数,输出 hello, worlddata.ok = false; // updateComponent 执行一次函数,输出 notdata.text = "hello, liang"; // updateComponent 会执行吗?此时 修改的话就不会再执行 了,因为第二次执行的时候,我们把 中 里的 清除了。data.textupdateComponentdata.textDepWatcher
"Vue2响应式系统之分支切换怎么实现"的内容就介绍到这里了,感谢大家的阅读。如果想了解更多行业相关的知识可以关注网站,小编将为大家输出更多高质量的实用文章!