持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第21天,点击查看活动详情
在上两篇文章vue2关于响应式原理源码详解(1) 和vue关于响应式原理源码详解(2)-Dep、Watcher 中主要了解了:Dep(观察目标类),Observe(可观测类),Watcher(观察者类),那么本文着重讲解依赖收集的具体流程。
依赖收集流程
/src/core/instance/init.ts
在首次渲染挂载的时候会调用mountComponent,会创建watcher
//渲染页面时会创建渲染watcher
export function mountComponent(vm, el)
{
...
let updateComponent = ()=>{
vm._update(vm._render()); // 渲染 、 更新
}
// 初始化就会创建watcher
let watcher = new Watcher(vm,updateComponent,()=>{
callHook(vm,'updated')
},true);
}
/src/core/instance/init.ts
接着进入new Watcher类,则会调用类中定义的this.get(),通过pushTarget(),将当前的渲染watcher挂载在dep.target属性上,待渲染完成后 将当前全局watcher删掉,执行cleanupDeps
class Watcher {
constructor(vm, exprOrFn, cb, options) {
...
this.value = this.get();
}
get() {
const vm = this.vm
pushTarget(this); // 当前watcher实例
let result = this.getter.call(vm, vm);
popTarget(); //渲染完成后 将当前全局watcher删掉了
return resultpopTarget();
cleanupDeps() ;
}
}
/src/core/instance/lifecycle.ts
执行 this.getter.call(vm, vm) ,也就是updateComponent() 函数
updateComponent = () => {
vm._update(vm._render(), hydrating)
}
/src/core/instance/render.ts
接着执行_render()函数,生成渲染vnode,在这个过程会访问vm的数据,从而触发数据对象的get方法,也就是说当前renderWatcher依赖了这些数据,所以会开始对它进行收集。
Vue.prototype._render = function (): VNode {
vnode = render.call(vm._renderProxy, vm.$createElement)
}
/src/core/observer/index.ts
于是就是触发到数据对象的get方法
get: function reactiveGetter () {
var value = val;
if (Dep.target) {
dep.depend();
if (childOb) {
childOb.dep.depend();
if (Array.isArray(value)) {
dependArray(value);
}
}
}
return value
},
/src/core/observer/dep.ts
当前的 Dep.target 是有值的,在触发get方法后会调用dep.depend()方法,也就是Dep.target.addDep(this)
depend(info?: DebuggerEventExtraInfo) {
if (Dep.target) {
Dep.target.addDep(this)
}
}
/src/core/observer/watcher.ts
又回到watcher类,将当前的 watcher 实例 push 到 subs 数组,这里就完成了依赖收集。
addDep(dep: 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)
}
}
}
/src/core/observer/dep.ts
addSub(sub: DepTarget) {
this.subs.push(sub)
}
总结
- 首先挂载之前会实例化一个渲染
watcher,进入new Watcher类会执行get()方法 - 在
get()方法中会执行pushTarget(this),也就是把Dep.target赋值为当前渲染watcher并压入栈 - 执行
this.getter.call(vm, vm),也就是updateComponent()函数,该方法执行了vm._update(vm._render(), hydrating) - 然后执行
vm._render()就会生成渲染vnode,这个过程中会访问 vm 上的数据,就触发了数据对象的 getter - 每一个对象值的 getter 都有一个
dep,在触发 getter 的时候就会调用dep.depend()方法,也就会执行Dep.target.addDep(this) - 然后这里会做一些判断,从而确保同一数据不会被多次添加,接着把当前的 watcher 实例 push 到 subs 里,到这就已经完成了依赖的收集,不过到这里还没执行完,如果是对象还会递归对象触发所有子项的getter,还要恢复 Dep.target 状态
大致过程如下: observe->defineReactive->get->dep.depend()-> watcher.addDep(new Dep()) -> watcher.newDeps.push(dep) -> dep.addSub(new Watcher()) -> dep.subs.push(watcher)