watcher与dep的关系程序运行分析
初始化执行 _init => initState() + vm.$mounted
initState => initData + initWatch + initComputed
- initData 中执行observer(),进行响应式处理
- 创建Observer类 => Observer类中对data进行遍历defineReactive,修改get和set函数
- defineReactive函数中创建私有变量dep(new Dep),每一个get和set对应一个dep
- get中判断Dep类的静态属性target有值,当前watcher的deps添加当前dep,当前dep的subs添加当前watcher;并返回value;
- set中赋值后执行dep.notify函数,执行dep的subs中的watcher循环update(执行queueWatcher=>flushWatcher=>延迟队列queue中watcher.run=>执行再次更新);
- initWatch
- 截取watch的key属性和监听需要处理的handler函数
- 对每一个key创建一个watcher,watcher函数中如果有user则获取到值后再次执行handler函数,实现监听功能
// vm key handler
new Watcher(vm,exprOrfn,handler,{...options,user:true}
- initComputed
- 获取到所有computed属性的key,handler
- 创建vm._computedWatcher,遍历赋值key:new Watcher
for(let key in computed){
// getters 为computed的handler计算函数
// lazy:true在watcher中区分时computed的属性,执行函数为计算函数
// watcher中用dirty取初始化lazy,默认为true,表示第一次或者需要再次执行handler计算函数,为false读取缓存的值,即watcher的value
_computedWatcher[key] =
new Watcher(vm,getters||handler,()=>{},{lazy:true})
- 遍历computed,进行defineComputed,对每一个key进行劫持修改get函数
- get的值为_computedWatcher对应key的watcher.value
- 如果dirty为true(默认值,或修改值后赋为true),执行watcher.evaluate方法即computed的handler方法,执行完dirty = false
- 判断如果Dep.target有,收集渲染watcher
vm.$mounted 挂载dom
- 拿到vm.el的outerHTML
- 转换为ast语法树
- 转为render函数(_s 解析变量 _v解析文本,_c解析标签),通过with函数实现解析_s函数this.变量的值。
- 执行mountedComponent函数
- 执行各个运行的钩子函数(beforeMounted,updated,mounted)
- 创建watcher实例 (lifecyle.js)
// _render拿到的是虚拟dom解构数据
let updateComponent = ()=>{
vm._update(vm._render())
}
new watcher(vm,updateComponent,()=>{
callHook(vm,'updated') //订阅
},true)
- watcher中执行get方法
pushTarget(this)
const value = this.getters.call(this.vm,this)
popTarget()
- pushTarget:先给Dep添加当前watcher至target
- 执行getters即_update方法:通过with函数实现解析_s函数this.变量的值,此时会调用该变量的obsercer中定义的get方法,Dep.target有值,执行dep.depend(),进行watcher dep互相收集,最后移除Dep的target
// watcher为当前渲染watcher,dep为每一个definReactive中的私有变量 dep = new Dep()
dep.depend() => Dep.target.addDep(this) => watcher.addDep(dep) =>
watcher.addDep(dep)=>{
// 在watcher中deps添加dep
this.deps.push(dep)
this.depsId.add(id)
//dep中subs也添加当前watcher
dep.addSub(this(watcher))
}
- 运行patch函数,通过diff算法实现最小量更新渲染真实DOM
总结
new watcher的地方有三处
- lifecycle中:mountComponent方法挂载dom。此watcher为一个渲染watcher,自动执行的get函数为渲染dom函数,回调函数为upadted钩子函数
new watcher(vm,updateComponent,()=>{callHook(vm,'updated') },true)
- initState中,initWatch方法,对每一个key创建一个watcher,watcher函数中如果有user则获取到值后再次执行handler函数,实现监听功能
// vm key handler
new Watcher(vm,exprOrfn,handler,{...options,user:true}
new watcher 默认执行get函数,读取一次vm.data的值,调用observe的get,进行依赖收集,defineReactive函数中的私有变量dep再次收集watch监听的watcher。set该变量时,dep.notify会挨个通知执行subs中的watcher
- initState中,**执行initComputed方法,此watcher是一个计算watcher,get函数为computed配置项里的计算函数。**无回调函数,options参数lazy=true,在watcher中区分是computed的watcher,初始化watcher不执行get函数。读取computed属性时,触发defineProperty劫持的getter方法,最终取值为watcher.value。其中如果第一次取值dirty为ture,计算watcher.evaluate,缓存value。以后取值读取缓存value,dirty赋为false,除非调用该watcher的update方法,dirty为true,再次调用才会重新计算evaluate
new Watcher(vm,getters||handler,()=>{},{lazy:true})
Q:computed的get方法中:判断if(Dep.target)再进行watcher.depend()互相收集依赖,Dep.target不是每次赋值完就pop吗?Dep中,stack什么情况下length>1?
A:当计算computed的值时,取到vm的变量,触发observe中的getter,此时Dep.target为computed的watcher,再observe中
会将各个变量 fisrtName lastName的dep中push computed-watcher,再computed-watcher的deps中push firstName和
lastName的dep.
new Dep
在给每个data进行响应式处理时,defineReactive函数里定义私有变量,dep=Dep(),没一个变量都可以找到一个对应的dep
Q: 一个变量对应一个dep,对应一个渲染watcher,可以有多个computed watcher,一个watch watcher, 对不对?
A:一个变量对应一个dep:data中每一个变量进行数据劫持(定义getter)时,定义一个私有变量dep,只能通过变量getter时获取得到
watcher有以下几种
- 渲染watcher 即render watcher, 在初始化数据劫持后执行后执行,初始执行函数为vm._update 渲染dom
- computed watcher,1 执行完毕后执行初始化绑定opiton里的wathcer配置项,主执行函数为计算函数,计算时会取vm的变量,调用observe的getter,此时Dep.target有值,会进行子变量的dep和当前计算watcher的互相收集
- watch watcher,1 执行完毕后初始化,拿到options watch配置的key和handler,初始化watcher,此时主函数是个字符串key,初始执行为自定义watcher的getter计算watcher.value来获取oldValue,该过程中调用vm.变量,同样进行2中的互相收集,该watcher的cb函数为配置项的handler:即监听后的用户处理回调;最后一个配置项user:true,用来区分update时,获取到新值后再执行回调函数。
附上本人github的学习仓库 learn-vue2-source-code (有大量注释)
p:求求给点个star吧😍