关于Dep 和 Watcher的 关系
先捋一下,Vue源码的执行逻辑
一、关于 computed 和 watch 中的 Watcher
1. 首先开始初始化
一开始初始化Data(initData函数)的时候, 执行逻辑为
observe -> new Observer(data) -> defineReactive -> get() ->
defineReactive为 如下
function defineReactive(data,key,value){
let dep = new Dep(); // 这个dep 是为了给对象使用的
// 这里这个value可能是数组 也可能是对象 ,返回的结果是observer的实例,当前这个value对应的observer
let childOb = observe(value); // 数组的observer实例
Object.defineProperty(data,key,{
configurable:true,
enumerable:true,
get(){ // 获取值的时候做一些操作
// 每个属性都对应着自己的watcher
if(Dep.target) { // 如果当前有watcher
dep.depend(); // 意味着我要将watcher存起来
if(childOb){ // *******数组的依赖收集*****
childOb.dep.depend(); // 收集了数组的相关依赖
// 如果数组中还有数组
if(Array.isArray(value)){
dependArray(value);
}
}
}
return value;
},
set(newValue){ // 也可以做一些操作
// console.log('更新数据')
if(newValue === value) return;
observe(newValue); // 继续劫持用户设置的值,因为有可能用户设置的值是一个对象
value = newValue;
dep.notify(); // 通知依赖的watcher来进行一个更新操作
}
});
}
由于一开始没有Watcher实例,所以函数里面的里面实际上 if(Dep.target) 判断这个的时候 啥也没有. 也就还没依赖收集.
里面的dep.depend();就不需要收集
2. Watcher
Watcher类: src/observer/watcher.js
import {
pushTarget,
popTarget
} from './dep.js';
import { queueWatcher } from './schedular'
let id = 0;
class Watcher {
constructor(vm, exprOrFn, callback, options) {
this.vm = vm;
this.callback = callback;
this.options = options;
this.sync = options.sync;
this.user = options.user; // 用来标识watcher的状态
this.id = id++;
this.lazy = options.lazy;
this.dirty = this.lazy;
if (typeof exprOrFn === 'function') {
this.getter = exprOrFn; // 将内部传过来的回调函数 放到getter属性上
} else {
// 将getter方法 封装成功了取值函数
this.getter = function() { // 'a.b.c.d'
let path = exprOrFn.split('.');
let val = vm;
for (let i = 0; i < path.length; i++) {
val = val[path[i]];
}
return val;
}
}
this.depsId = new Set(); // es6中的集合 (不能放重复项)
this.deps = [];
// ✅ this.get()核心
this.value = this.lazy ? undefined : this.get(); // 调用get方法 会让渲染watcher执行
}
addDep(dep) { // watcher 里不能放重复的dep dep里不能放重复的watcher
let id = dep.id;
if (!this.depsId.has(id)) {
this.depsId.add(id);
this.deps.push(dep);
dep.addSub(this); // addSub是Dep类里面的方法,这里是将Watcher实例this 收集到 Dep的实例dep中
}
}
get() {
pushTarget(this); // 把watcher存起来 Dep.target = this
let value = this.getter.call(this.vm); // 渲染watcher的执行 this.vm = vm
popTarget(); // 移除watcher
return value
}
update() {
if (this.sync) {
this.run();
}else if(this.lazy){ // 计算属性依赖的值发生了变化
this.dirty = true;
} else {
queueWatcher(this);
// console.log(this.id)
// 等待着 一起来更新 因为每次调用update的时候 都放入了watcher
// this.get();
}
}
evaluate(){
this.value = this.get();
this.dirty = false;
}
run() {
let oldValue = this.value; // 第一次渲染的值
let newValue = this.get();
this.value = newValue;
if (this.user) { // 如果当前是用户watcher 就执行用户定义的callback
this.callback.call(this.vm, newValue, oldValue);
}
}
depend(){
let i = this.deps.length;
while(i--){
this.deps[i].depend();
}
}
}
// 下次课 会带大家看一次vue的源代码
// 在模板中取值时 会进行依赖收集 在更改数据是会进行对应的watcher 调用更新操作
// dep 和 watcher 是一个多对多的关系 dep里存放着相关的watcher 是一个观察者模式
export default Watcher
第一种
当mountComponent函数执行时, 进行组件渲染, 就会执行 new Watcher
执行渲染函数render
内部执行vm_render函数,会去vm取 在template模版中 使用的值, 然后就到了 vm的响应式部分 执行get方法... 以及watcher里面的其他逻辑(同下)
当render渲染 a组件, 将a组件的watcher实例放到Dep.target(全局唯一)上,render渲染的时候 template模版上的 name 就可以取到 name 变量,然后执行 获取name变量 响应式的 get 方法,并且在Dep和Watcher类中, 进行Dep实例收集Watcher,Watcher收集Dep
渲染逻辑render封装在Watcher中,先有watcher渲染的变量、才能找到响应式中的get
由于在外面vm.name取值的时候(非template模版中取值)。也会执行响应式里面的get方法,但是这个时候。不是渲染过程,所以。Dep.Target的值为空,就不会去收集依赖,只有在模板render里面取值,也就是渲染函数render中才会收集依赖
第二种
当初始化 computed的时候, 里面执行new Watcher的时候,
function initComputed(vm, computed) {
// _computedWatchers 存放着所有的计算属性对应的watcher
const watchers = vm._computedWatchers = {};
for (let key in computed) {
const userDef = computed[key]; // 获取用户的定义函数
const getter = typeof userDef === 'function' ? userDef : userDef.get;
// 获得getter函数 lazy:true 表示的就是computed 计算属性
// 内部会根据lazy属性,不去调用getter
watchers[key] = new Watcher(vm, getter, ()=>{} , {lazy:true})
// 计算属性可以直接通过vm来进行取值,所以将属性定义到实例上
defineComputed(vm,key,userDef);
}
}
接上
Watcher内部, 构造函数执行get函数,get函数 会执行 pushTarget 函数,给Dep.target=this赋值,this也就是(watcher实例),
get() {
pushTarget(this); // 把watcher存起来 Dep.target = this
let value = this.getter.call(this.vm); // 渲染watcher的执行 this.vm = vm
popTarget(); // 移除watcher
return value
}
然后在 get函数中 执行了一下 computed 计算属性 用户给的具体的options(也就是initComputed函数里面的getter变量),
所以就会触发options里面的数据的 get 方法(obverse响应式),
相同
然后执行第一部分《首先开始初始化》里面的逻辑
Observer类
get(){ // 获取值的时候做一些操作
// 每个属性都对应着自己的watcher
if(Dep.target) { // 如果当前有watcher
dep.depend(); // 意味着我要将watcher存起来
if(childOb){ // *******数组的依赖收集*****
childOb.dep.depend(); // 收集了数组的相关依赖
// 如果数组中还有数组
if(Array.isArray(value)){
dependArray(value);
}
}
}
return value;
},
这里Dep.target有指,接下来执行dep.depend();
Dep类里面的方法
addSub(watcher){
this.subs.push(watcher); // 观察者模式
}
depend(){
// 让这个watcher(Dep.target) 记住我当前的dep,如果watcher没存过dep ,dep肯定不能存过watcher
Dep.target.addDep(this); // 调用, 这里的this为Dep实例,将Dep实例传到Watcher的addDep方法中
}
Watcher类里面的方法
addDep(dep) { // watcher 里不能放重复的dep dep里不能放重复的watcher
let id = dep.id;
if (!this.depsId.has(id)) {
this.depsId.add(id);
this.deps.push(dep);
dep.addSub(this); // addSub是Dep类里面的方法,这里是将Watcher实例this 收集到 Dep的实例dep中
}
}
让watcher实例addDep收集Dep,
最后在Watcher类, 执行 dep.addSub(this), 让dep收集watcher实例,等于将Watcher实例收集到Dep的subs数组中。
至此完成双向收集。
比喻
将Watcher当成一个侦探,首先应该知道监听谁,也就是addDep,监听那个数据,然后收集一下数据对应的Dep,最后Dep也收集一些监听的类Watcher实例