vue2关于响应式原理源码详解(3)- 依赖收集

108 阅读2分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 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) 
}

总结

  1. 首先挂载之前会实例化一个渲染 watcher ,进入new Watcher类会执行 get() 方法
  2. get()方法中会执行 pushTarget(this),也就是把 Dep.target 赋值为当前渲染 watcher 并压入栈
  3. 执行 this.getter.call(vm, vm),也就是updateComponent() 函数,该方法执行了 vm._update(vm._render(), hydrating)
  4. 然后执行 vm._render() 就会生成渲染 vnode,这个过程中会访问 vm 上的数据,就触发了数据对象的 getter
  5. 每一个对象值的 getter 都有一个 dep,在触发 getter 的时候就会调用 dep.depend() 方法,也就会执行 Dep.target.addDep(this)
  6. 然后这里会做一些判断,从而确保同一数据不会被多次添加,接着把当前的 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)