Vue_MVVM 核心模块解析 - zen0822/interview GitHub Wiki
Vue MVVM 核心模块解析
图解
观察者模式(Observer, Watcher, Dep)
Vue 实现响应式有三个很重要的类,Observer类,Watcher类,Dep类
Dep 是依赖,Watcher 收集 Dep
- Observer 类主要用于给Vue的数据defineProperty增加getter/setter方法,并且在getter/setter中收集依赖或者通知更新
- Watcher 类来用于观察数据(或者表达式)变化然后执行回调函数(其中也有收集依赖的过程),主要用于$watch API和指令上
- Dep 类就是一个可观察对象,可以有不同指令订阅它(它是多播的)
观察者模式,跟发布/订阅模式有点像 但是其实略有不同,发布/订阅模式是由统一的事件分发调度中心,on 则往中心中数组加事件(订阅),emit 则从中心中数组取出事件(发布),发布和订阅以及发布后调度订阅者的操作都是由中心统一完成
但是观察者模式则没有这样的中心,观察者订阅了可观察对象,当可观察对象发布事件,则就直接调度观察者的行为,所以这里观察者和可观察对象其实就产生了一个依赖的关系,这个是发布/订阅模式上没有体现的。
如何实现观察者模式呢?
我们先看下面代码,下面代码实现了 Watcher 去订阅 Dep 的过程,Dep 由于是可以被多个 Watcher 所订阅的,所以它拥有着订阅者数组,订阅了它,就把 Watcher 放入数组即可。
class Dep {
constructor () {
this.subs = []
}
notify () {
const subs = this.subs.slice()
for (let i = 0; i < subs.length; i++) {
subs[i].update()
}
}
// 添加 Watcher
addSub (sub) {
this.subs.push(sub)
}
}
class Watcher {
constructor () {
}
update () {
}
}
let dep = new Dep()
dep.addSub(new Watcher()) // Watcher订阅了依赖
我们实现了订阅,那通知发布呢,也就是上面的 notify 在哪里实现呢?
我们到这里就可以联系到数据响应,我们需要的是数据变化去通知更新,那显然是会在 defineProperty 中的 setter 中去实现了,聪明的你应该想到了,我们可以把每一个数据当成一个 Dep 实例,然后 setter 的时候去 notify 就行了,所以我们可以在 defineProperty 中 new Dep(),通过闭包 setter 就可以取到 Dep 实例了
在每一个可观察的对象中 get 中是可以取到这个 Dep 实例的,所以可以在执行 watch(订阅) 操作的时候,执行获取数值,触发 getter 去收集依赖
function defineReactive (obj, key, val) {
const dep = new Dep()
const property = Object.getOwnPropertyDescriptor(obj, key)
const getter = property && property.get
const setter = property && property.set
let childOb = observe(val)
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: function reactiveGetter () {
const value = getter ? getter.call(obj) : val
if (Dep.target) {
dep.depend() // 触发当前依赖 Dep 加入 Dep.target(Watcher.addDep(this))
if (childOb) {
// 子元素变成了可观察者对象,Dep.target 触发变成当前的 Watcher 对象,
// 所以需要将当前的 Dep.target 加入到这个可观察者对象
childOb.dep.depend()
if (Array.isArray(value)) {
dependArray(value)
}
}
}
return value
},
set: function reactiveSetter (newVal) {
const value = getter ? getter.call(obj) : val
if (newVal === value) {
return
}
if (setter) {
setter.call(obj, newVal)
} else {
val = newVal
}
childOb = !shallow && observe(newVal) // 看看新值是不是可以变成可观察对象的
dep.notify()
}
})
}
结合 Watcher 模块
// 为Dep.target 赋值
function pushTarget (Watcher) {
Dep.target = Watcher
}
function popTarget (Watcher) {
Dep.target = null
}
class Watcher () {
constructor (vm, expOrFn, cb, options) {
//传进来的对象 例如Vue
this.vm = vm
//在 Vue 的 render 中 cb 是更新视图的核心,调用 diff 并更新视图的过程
this.cb = cb
//收集 Deps,用于移除监听
this.newDeps = []
// Dep 依赖的 getter 方法,也就是调用获取这个变量的值
if (typeof expOrFn === 'function') {
this.getter = expOrFn
} else {
this.getter = parsePath(expOrFn)
if (!this.getter) {
this.getter = function () {}
}
}
//设置Dep.target的值,依赖收集时的watcher对象
this.value =this.get()
}
get () {
pushTarget(this) // 标记全局变量 Dep.target
let value = this.getter.call(vm, vm) // 触发 getter,收集依赖(将观察者加入可观察对象)
if (this.deep) {
traverse(value)
}
popTarget() // 标记全局变量 Dep.target 为 null,将当前的依赖对象回退到上一个状态
return value
}
// 订阅的 Dep 会 notify 通知 Watcher 更新值和执行回调函数
update () {
this.run()
}
run () {
const value = this.get() // new Value
// re-collect dep 重复收集依赖,看看新的值是不是观察者来的
if (value !== this.value ||
isObject(value)) {
const oldValue = this.value
this.value = value
this.cb.call(this.vm, value, oldValue)
}
}
}