Appearance
页面更新
javascript
// 页面的初始化渲染及后续页面响应式更新都是通过 updateComponent 实现的,当数据变化时,
// 重新调用 updateComponent 将 render 函数 => VNode => DOM,由于 render函数中
// 的变量会去 data 中取,所以 data 改变后就会生成不同的 vnode,从而生成不同的 dom,
// 实现了数据变化引起视图变化。我们需要实现当 data 变化时重新调用 updateComponent,
// 通过观察者模式实现,它定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,
// 所有依赖于它的对象都将得到通知。
// mountComponent 内部最后 new Watcher,Watcher 类构造器中接收
// vm、expOrFn、cb,options 四个参数,对于更新视图的 render watcher 来说
// expOrFn 就是 updateComponent,options 则是 true,被用来标识是 render watcher
// 构造器中 expOrFn 会被挂载到 this.getter 上,原型中定义 get 方法,get 内部会执行
// this.getter 用于初始化渲染及响应式更新,构造器最后会执行 this.get,即
// updateComponent 实现页面的初始化渲染,原型中有个 update 方法,后续的响应式更新
// 就是在 update 方法中执行 this.get 方法从而执行 getter,即 updateComponent
// 实现视图更新。当然实际上,由于要实现异步更新,update 中不直接执行 this.get,而是
// 执行 queueWatcher,queueWatcher 中让每个 watcher 执行 this.run,this.run 中执行
// this.get。
// 如何实现 data 变化时就调用 render watcher 上的 update 方法?让每个属性收集用到它的
// render watcher,属性变化时,通知 render watcher 调用 this.update。
// dep 和 watcher 其实是互相收集的,至于 watcher 为什么要收集 dep,后面再讨论。
// Dep 类的构造器中会生成 id 用于标识 dep,让 watcher 收集 deps 时能
// 去重,subs 用于记录这个 dep 收集的 watchers
// Watcher 类的构造器中也会生成 id 用于标识 watcher,deps 用于记录这个 watcher
// 收集的 deps,depIds 是一个 Set 集合,用于实现 dep 去重。
// 互相收集的具体过程就是 Dep 类上添加一个属性 target 用于记录当前 watcher,初始化渲
// 染时会调用 get,get 内部在调用 this.getter 之前,通过调用 pushTarget(this) 让
// Dep.target 记录当前 render watcher,之后执行 this.getter进行初始化渲染,初始化
// 渲染时会去 data 中取值 => _s(name),由于我们劫持了 data 的所有层次的对象的属性,
// 在 Object.defineProperty 的 get 中调用 dep.depend 收集让 dep 收集render watcher,
// Dep 实例在哪里生成?由于每个属性都是一个 dep,在 defineReactive中
// const dep = new Dep() 即可,dep.depend 并不直接收集 render watcher,而是内部
// 调用 Dep.target.addDep(this) 先让 watcher 收集 dep,Dep.target 此时就是
// render watcher,this 就是 dep,addDep 内部会取出 dep.id,如果 Set 中不包含这个
// id 则 this.deps.push(dep)、this.depIds.add(id) 完成render watcher收集dep
// 收集完成后再 dep.addSub(this) 让 dep 收集 render watcher,由于 watcher 已经
// 去重了 dep,dep 收集到的 render watcher 自然不会是重复的,这样就完成了
// dep 和 render watcher 的互相收集,最后在 this.getter 最后通过 popTarget 清除记录。
// 当数据被修改时会触发 Object.defineProperty 中的 set,在 set 中调用 dep.notify,
// 内部会让该 dep 收集的每一个 watcher 都调用update,update 中执行 queueWatcher,
// queueWatcher 中让每个 watcher 执行 this.run,this.run 中执行this.get 重新渲染
// 页面,实现响应式更新。
class Watcher implements DepTarget {
constructor(
vm: Component | null,
expOrFn: string | (() => any),
cb: Function,
options?: WatcherOptions | null
) {
this.id = ++uid // uid for batching
this.deps = [] // watcher 收集的 deps
this.depIds = new Set() // 保存收集过的 dep 的 id,用于 dep 去重。
this.getter = expOrFn
this.value = this.get()
}
get() {
// 记录当前 watcher
pushTarget(this)
let value
const vm = this.vm
value = this.getter.call(vm, vm)
// 清除记录
popTarget()
}
// watcher 添加 dep
addDep(dep: Dep) {
const id = dep.id
if (!this.depIds.has(id)) {
this.depIds.add(id)
this.deps.push(dep)
dep.addSub(this)
}
}
update() {
queueWatcher(this)
}
run() {
this.get()
}
}
class Dep {
constructor() {
this.id = uid++
this.subs = [] // dep 收集的 watchers
}
// dep 收集 watcher
addSub(sub: DepTarget) {
this.subs.push(sub)
}
depend(info?: DebuggerEventExtraInfo) {
if (Dep.target) {
Dep.target.addDep(this)
}
}
notify(info?: DebuggerEventExtraInfo) {
const subs = this.subs.slice()
for (let i = 0, l = subs.length; i < l; i++) {
subs[i].update()
}
}
}
function defineReactive(
obj: object,
key: string,
val?: any,
customSetter?: Function | null,
shallow?: boolean,
mock?: boolean
) {
// 每个属性收集使用它们的 watcher
const dep = new Dep()
// 为该属性实现响应式
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: function reactiveGetter() {
// 如果模板外使用数据,不进行收集操作。
if (Dep.target) {
// 互相收集
dep.depend()
}
},
set: function reactiveSetter(newVal) {
// 通知 render watcher 更新
dep.notify()
}
})
return dep
}