Vue源码研究02-依赖收集-数据变化后应该通知依赖这个数据的DOM
2.1 什么是依赖收集?
当定义在data中的一个数据发生变化时,应该通知页面中的那些DOM更新显示呢?如果更新整个页面的话,重绘、重排、页面效率低,显示不合理。
对,谁用到了这个数据(换句话,==谁依赖了这个数据==),就通知谁变化。每个数据对应一个==依赖数组==,谁依赖了这个数据,就把谁放到这个数据对应的依赖数组中。当这个数据变化时,就把这个数据对应的依赖数组中的依赖,挨个通知下,让他们重新渲染下视图。这个==过程==就是依赖收集。
2.2 什么时候收集依赖?什么时候通知依赖更新?
读数据的时候,收集依赖;写数据的时候,通知依赖更新。
读数据的时候会触发getter方法;写数据的时候会触发setter方法。
因此,在getter方法中收集依赖,在setter方法中通知依赖更新。
2.3 依赖是什么?
谁用到了这个数据谁就是依赖,在Vue中定义了一个==Watcher类==来描述这个依赖,一个依赖就是一个Watcher实例。一个Watcher实例,是一个观察者,是一个哨兵。可以思考下这里是设计模式中的那种模式呢?是观察者模式还是发布订阅模式?Vue源码的设计中用到了设计模式和面向对象思想。
实例的代码描述如下:
class Watcher {
constructor (vm, expOrFn, cb, options) {
this.cb = cb; // 回调
this.vm = vm; // 当前Vue实例
this.getter = parsePath(expOrFn)
this.value = this.get() // 获取下被依赖的数据,目的是触发数据的get方法
}
get () {
window.target = this; // 将当前Watcher赋给全局的唯一对象
const vm = this.vm
let value = this.getter.call(vm, vm) // 获取被依赖的数据
window.target = undefined; // 释放 window.target
return value
}
update () { // 更新视图的方法
const oldValue = this.value
this.value = this.get()
this.cb.call(this.vm, this.value, oldValue)
}
}
/**
* Parse simple path.
* 把一个形如'data.a.b.c'的字符串路径所表示的值,从真实的data对象中取出来
* 例如:
* data = {a:{b:{c:2}}}
* parsePath('a.b.c')(data) // 2
*/
const bailRE = /[^\w.$]/
export function parsePath (path) {
if (bailRE.test(path)) {
return
}
const segments = path.split('.')
return function (obj) {
for (let i = 0; i < segments.length; i++) {
if (!obj) return
obj = obj[segments[i]]
}
return obj
}
}
实例化Watcher会执行其构造方法。在构造方法中会调用this.get()实例方法,将当前实例==挂载到window.target上==,并获取下被依赖的数据,目的是==触发数据的getter方法==,getter方法中会调用dep.depend()收集依赖,而在dep.depend中会==获取挂载到window.target==上的Watcher存入到依赖数组中,最后释放掉window.target。
2.4 依赖管理器
2.1节中,我们说每个数据对应一个依赖数组,谁依赖了这个数据,我们就把谁(Watcher)放入到依赖数组中。不过,只用一个数组去存放管理依赖,功能很难满足我们场景的需求。因此,我们定义一个类,好像一个依赖管理器,存放和管理数据对应的依赖。
class Dep { // 一个依赖收集类,一个依赖的集合
constructor () {
this.subs = [] // 用来存放Watcher对象的数组
}
addSub (sub) { // 在subs中添加一个Watcher对象
this.subs.push(sub)
}
removeSub (sub) { // 从subs中删除一个Watcher对象
remove(this.subs, sub)
}
notify () { // 通知所有Watcher对象更新视图
const subs = this.subs.slice()
for (let i = 0, l = subs.length; i < l; i++) {
subs[i].updte()
}
}
}
数据变化时,会触发setter方法,在setter中调用实例的notify方法,在notify方法中会遍历依赖中的所有watcher实例,并调用watcher实例的update方法更新视图。
2.5 图解依赖收集和视图更新的过程
