上篇,主要介绍了依赖收集过程中 dep 和 watcher 关联:
利用 js 单线程特性,在 Watcher 类中 get 方法即将触发视图更新前,
利用全局的类静态属性 Dep.target 记录 Watcher 实例
并且,在视图渲染的取值过程中,在Object.defineProperty的get方法中,让数据的dep记住渲染watcher
从而,实现了 dep 与 watcher 相关联,只有参与视图渲染的数据发生变化才会触发视图的更新
本篇,继续依赖收集的视图更新部分
问题:同一数据在视图中多次使用会怎样?
按照当前逻辑,同一数据在一个视图中被多次使用时,相同 watcher 会在 dep 中多次保存
{{name}} {{name}} {{name}}
这时 name 的 dep 中,会保存三个相同的渲染 watcher
所以,watcher 需要进行查重
同 Dep 类的做法:给 Watcher 添加一个 id,
每次 new Watcher 是 id 自增,以此作为标记对 watcher 实例进行查重
let id = 0;
class Watcher {constructor(vm, fn, cb, options){this.vm = vm;this.fn = fn;this.cb = cb;this.options = options;this.id = id++; // watcher 唯一标记this.getter = fn;this.get();}get(){Dep.target = this;this.getter();Dep.target = null;}
}
export default Watcher
前面,让数据的dep记住了渲染 watcher
同样的,watcher 也有必要记住 dep
let id = 0;class Dep {constructor(){this.id = id++;this.subs = [];}// 让 watcher 记住 dep(查重),再让 dep 记住 watcherdepend(){// 相当于 watcher.addDep:使当前 watcher 记住 depDep.target.addDep(this); }// 让 dep 记住 watcher - 在 watcher 中被调用addSub(watcher){this.subs.push(watcher);}
}Dep.target = null; // 静态属性,用于记录当前 watcherexport default Dep;
为什么要这样实现?
如果要互相记住,watcher 中要对 dep 做查重;dep 中也要对 watcher 做查重;
用这种方法使 dep 和 watcher 关联在一起后,只要判断一次就可以了
import Dep from "./dep";
let id = 0;
class Watcher {constructor(vm, fn, cb, options){this.vm = vm;this.fn = fn;this.cb = cb;this.options = options;this.id = id++;this.depsId = new Set(); // 用于当前 watcher 保存 dep 实例的唯一idthis.deps = []; // 用于当前 watcher 保存 dep 实例this.getter = fn;this.get();}addDep(dep){let did = dep.id;// dep 查重 if(!this.depsId.has(did)){// 让 watcher 记住 depthis.depsId.add(did);this.deps.push(dep);// 让 dep 也记住 watcherdep.addSub(this); }}get(){Dep.target = this;this.getter();Dep.target = null;}
}export default Watcher;
这种实现方式,会让 dep 和 watcher 保持一种相对的关系:
如果 watcher 中存过 dep;那么 dep 中一定存过 watcher ;
如果 watcher 中没存过 dep;那么 dep 中一定没存过 watcher ;
所以只需要判断一次,就能完成 dep 和 watcher 的查重;
当视图更新时,会进入 Object.defineProperty 的 set 方法
需要在 set 方法中,通知 dep 中所有收集的 watcher,执行视图更新方法
// src/observe/index.js#defineReactivefunction defineReactive(obj, key, value) {observe(value);let dep = new Dep(); // 为每个属性添加一个 depObject.defineProperty(obj, key, {get() {if(Dep.target){dep.depend();}return value;},set(newValue) {if (newValue === value) returnobserve(newValue);value = newValue;dep.notify(); // 通知当前 dep 中收集的所有 watcher 依次执行视图更新}})
}
let id = 0;class Dep {constructor(){this.id = id++;this.subs = [];}depend(){Dep.target.addDep(this); }addSub(watcher){this.subs.push(watcher);}// dep 中收集的全部 watcher 依次执行更新方法 updatenotify(){this.subs.forEach(watcher => watcher.update())}
}
Dep.target = null;
export default Dep;
import Dep from "./dep";
let id = 0;
class Watcher {constructor(vm, fn, cb, options){this.vm = vm;this.fn = fn;this.cb = cb;this.options = options;this.id = id++;this.depsId = new Set();this.deps = [];this.getter = fn;this.get();}addDep(dep){let did = dep.id;if(!this.depsId.has(did)){this.depsId.add(did);this.deps.push(dep);dep.addSub(this); }}get(){Dep.target = this;this.getter();Dep.target = null;}// 执行视图渲染逻辑update(){this.get();}
}
export default Watcher;
多次频繁更新同一个数据,会使视图频繁进行重新渲染操作
let vm = new Vue({el: '#app',data() {return { name: "Brave" , age: 123}}
});
vm.name = "Brave Wang";
vm.name = "Brave";
vm.name = "Brave Wang";
vm.name = "Brave";
vm.name = "Brave Wang";
vm.name = "Brave";
name的值变化了 6 次,但最终其实没有变化还是 Brave,
这里就需要改为做异步更新的机制
本篇,介绍了 Vue依赖收集的视图更新部分,主要涉及以下几点:
视图初始化时:
数据更新时:
下一篇:Vue 异步更新
20210802:修改文章摘要