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)
拦截了 中 和 的 ,并且各自初始化了一个 实例,用来保存依赖它们的 对象。data
text
ok
get、set
Dep
Watcher
new Watcher(updateComponent)
这一步会执行 函数,执行过程中用到的所有对象属性,会将 收集到相应对象属性中的 中。updateComponent
Watcher
Dep
当然这里的 其实是同一个,所以用了指向的箭头。Watcher
data.ok = false
这一步会触发 ,从而执行 中所有的 ,此时就会执行一次 。set
Dep
Watcher
updateComponent
执行 就会重新读取 中的属性,触发 ,然后继续收集 。updateComponent
data
get
Watcher
重新执行 函数 的时候:updateComponent
const updateComponent = () => { console.log("收到", data.ok ? data.text : "not");};
因为 的值变为 ,所以就不会触发 的 , 的 就不会变化了。data.ok
false
data.text
get
text
Dep
而 会继续执行,触发 收集 ,但由于我们 中使用的是数组,此时收集到的两个 其实是同一个,这里是有问题,会导致 重复执行,一会儿我们来解决下。data.ok
get
Watcher
Dep
Wacher
updateComponent
data.text = "hello, liang"
执行这句的时候,会触发 的 ,所以会执行一次 。但从代码来看 函数中由于 为 , 对输出没有任何影响,这次执行其实是没有必要的。text
set
updateComponent
updateComponent
data.ok
false
data.text
之所以执行了,是因为第一次执行 读取了 从而收集了 ,第二次执行 的时候, 虽然没有读到,但之前的 也没有清除掉,所以这一次改变 的时候 依旧会执行。updateComponent
data.text
Watcher
updateComponent
data.text
Watcher
data.text
updateComponent
所以我们需要的就是当重新执行 的时候,如果 已经不依赖于某个 了,我们需要将当前 从该 中移除掉。updateComponent
Watcher
Dep
Watcher
Dep
问题
总结下来我们需要做两件事情。
去重, 中不要重复收集 。
Dep
Watcher
重置,如果该属性对 中的 已经没有影响了(换句话就是, 中的 已经不会读取到该属性了 ),就将该 从该属性的 中删除。
Dep
Wacher
Watcher
updateComponent
Watcher
Dep
去重
去重的话有两种方案:
Dep
中的 数组换为 。subs
Set
每个 对象引入 , 对象中记录所有的 的 ,下次重新收集依赖的时候,如果 的 已经存在,就不再收集该 了。
Dep
id
Watcher
Dep
id
Dep
id
Watcher
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; // 静态变量,全局唯一
在 中,我们引入 来记录所有的 。Watcher
this.depIds
id
import Dep from "./dep";export default class Watcher { constructor(Fn) { this.getter = Fn; this.depIds = new Set(); // 拥有 has 函数可以判断是否存在某个 id this.get(); } get() { Dep.target = this; // 保存包装了当前正在执行的函数的 Watcher let value; try { value = this.getter.call(); } catch (e) { throw e; } finally { this.cleanupDeps(); } return value; } addDep(dep) { const id = dep.id; if (!this.depIds.has(id)) { dep.addSub(this); } } update() { this.run(); } run() { this.get(); }}
重置
同样是两个方案:
全量式移除,保存 所影响的所有 对象,当重新收集 的前,把当前 从记录中的所有 对象中移除。
Watcher
Dep
Watcher
Watcher
Dep
增量式移除,重新收集依赖时,用一个新的变量记录所有的 对象,之后再和旧的 对象列表比对,如果新的中没有,旧的中有,就将当前 从该 对象中移除。
Dep
Dep
Watcher
Dep
Vue2
中采用的是方案 ,这里也实现下。2
首先是 类,我们需要提供一个 方法。Dep
removeSub
import { remove } from "./util";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; // 静态变量,全局唯一
然后是 类,我们引入 来保存所有的旧 对象,引入 来保存所有的新 对象。Watcher
this.deps
Dep
this.newDeps
Dep
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(); } get() { Dep.target = this; // 保存包装了当前正在执行的函数的 Watcher let value; try { value = this.getter.call(); } catch (e) { throw e; } finally { this.cleanupDeps(); } return value; } 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); } } } 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; } update() { this.run(); } 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.text
updateComponent
data.text
Dep
Watcher
“Vue2响应式系统之分支切换怎么实现”的内容就介绍到这里了,感谢大家的阅读。如果想了解更多行业相关的知识可以关注编程网网站,小编将为大家输出更多高质量的实用文章!
免责声明:
① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。
② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341