前言
数据被更改之后,vue需要把数据生成虚拟结点,然后渲染到页面上,既分别调用_render和_update方式,但是调用这两个方法的过程(vm._update(vm._render()))不应该由用户去执行,而应该由程序在数据被更改之后自动去调用。这就是依赖收集和更新过程。
封装方法
vue更新策略是以组件为单位的,给每一个组件都增加一个watcher,属性变化后会更新调用这个watcher(渲染watcher)
创建Watcher类,把vm._update(vm._render())封装在Watcher里,只需要通过调用Watcher实例上的get方法就可以重新渲染。
import { pushTarget, popTarget } from "./dep";
let id = 0;
class Watcher {
constructor(vm, exprOrFn, cb, options) {
this.vm = vm
this.exprOrFn = exprOrFn
this.cb = cb
this.options = options
this.id = id++ // watcher唯一标识
this.deps=[] // watcher记录有多少dep依赖它
this.depsId = new Set();
if (typeof exprOrFn == 'function') {
this.getter = exprOrFn
}
this.get() ;// 默认调用getter方法
}
addDep(dep) {
let id = dep.id
if(!this.depsId.has(id)){
this.deps.push(dep)
this.depsId.add(id)
dep.addSub(this)
}
}
get() {
pushTarget(this);
this.getter() // 调用exprOrFn 渲染页面 取值(执行了get方法)
popTarget()
}
update() {
this.get();
}
}
export default Watcher; // 默认调用exprOrFn方法
属性与watcher的关系
很显然,一个组件内往往会存在多个属性,也就意味一个watcher与多个属性绑定在一起,这些属性的变化都会引起watcher的渲染;同样地,一个属性也可能会引起多个watcher的变化(如:用户创建了vm.$watcher)。我们创建一个Dep类,一个属性对应一个Dep实例用来收集watcher。因此一个watcher和一个dep存在多对多的关系。
先上代码,然后再分析watcher和dep之间建立关系的过程:
function defineReactive(data,key,value) {
// 获取到数组对应的dep
let childDep = observe(value) // 如果值是对象再进行观测
let dep = new Dep(); // 每个属性都有一个dep
Object.defineProperty(data,key,{
get() { // 依赖收集
if(Dep.target) { //让这个属性记住这个watcher
dep.depend();
if(childDep){
childDep.dep.depend();
}
}
return value
},
set(newValue) { // 依赖更新
if(newValue == value) return;
observe(newValue) // 如果用户将值设置为对象仍然需要进行观测
value = newValue;
dep.notify();
}
})
}
let id=0;
class Dep {
constructor() {
this.subs=[]
this.id= id++
}
depend() {
// watcher和dep双向记忆
Dep.target.addDep(this)
// this.subs.push(Dep.target);
}
addSub(watcher) {
this.subs.push(watcher);
}
notify() {
this.subs.forEach(watcher=>watcher.update())
}
}
Dep.target = null;
export function pushTarget(watcher) {
Dep.target = watcher // 保留watcher
}
export function popTarget() {
Dep.target = null;
}
export default Dep;
整个过程如下: 1.在第一次页面渲染的时候,会调用watcher上的get方法,这时就会将当前的渲染watcher挂载到Dep的target静态属性上。 2.然后执行getter方法,渲染页面的时候会取值(执行了get方法),进入到defineReactive中的get方法,给每一个属性创建一个dep。如果当前是渲染过程,就把当前这个数据添加到watcher的deps上(这个watcher上依赖),同时也把这个watcher添加到dep的subs上,实现互相依赖。 3.清除Dep的target属性。并不需要给每个属性加dep,没参与到渲染过程的也就不会出现在页面上。 4.当发生数据变更的时候,执行了defineReactive中的set方法,就会触发dep的notify方法,这个属性上所依赖的watcher就会执行更新操作。
总结
几个重点:
1.在Dep的target上挂载渲染watcher,在渲染完成后,需要清除watcher。
依赖收集只需要收集在页面渲染中用到的属性。而在组件中用到的其他属性,并不需要进行依赖收集。因此在页面渲染完成后,将watcher清除。
2.属性进行依赖收集和依赖更新的过程。
每一个属性都有一个对应dep属性,在dep中使用了发布订阅模式,把当前属性所依赖的watcher收集起来。在属性发生变更时,触发所依赖的watcher上的update方法。