我们继续学习 Dep 类,这个类在响应式中也是很重要的类,在实例化 Observer 类对象的时候就创建了一个 Dep 实例对象保存在 Observer 对象的成员变量中,这就是一对一的关系,也就是说 ob 与 Dep 实例是一对一关联的,文章配有大量生动形象的图,不要慌。我们先看下它的 UML 类图结构
在研究 Dep 的代码之前我们先来简单学习下一种数据结构——栈,它是一种线性存储结构,规定一端为栈底,一端为栈顶,每次插入(入栈或压栈)与删除(出栈或弹栈)操作都只能在栈顶操作。栈中的数据元素遵守“后进先出”的原则,也可以说是“先进后出"(First In Last Out)的原则。栈的实现有基于链表的,也有基于数组的,也还有其他的,这个不同语言根据不同需求实现即可。
Dep 的实例化场景只有两个,一个是在 Observer 类的构造函数中,另一个是在 defineReactive 函数中。
export class Observer {
...
constructor (value: any) {
this.value = value
this.dep = new Dep() // <----
this.vmCount = 0
...
}
}
在 Observer 构造函数中实例化了一个 Dep 对象,并赋值给成员变量 dep,这样 Observer 对象就与 Dep 对象一对一关联起来。
export function defineReactive (
obj: Object, key: string, val: any,
customSetter?: ?Function, shallow?: boolean
) {
const dep = new Dep() // <----
const property = Object.getOwnPropertyDescriptor(obj, key)
const getter = property && property.get
...
Object.defineProperty(obj, key, {
...
get: function reactiveGetter () {
const value = getter ? getter.call(obj) : val
if (Dep.target) {
dep.depend()
...
}
return value
},
set: function reactiveSetter (newVal) {
...
dep.notify()
}
})
}
在 defineReactive 函数中实例化了一个 Dep 对象,并赋值给了局部变量 dep,在用 Object.defineProperty 定义的响应式属性的 reactiveGetter\reactiveSetter 方法中引用了这个局部变量 dep,这是利用闭包来保存属性的相关信息,其他还有比如 val、obj 和 getter\setter 等。
经过 defineReactive 处理后的对象属性具备有响应式功能,且每个属性通过在闭包中保存了一份 Dep 对象及其他必要信息,最后,对象的属性与 Dep 对象同样也实现了一对一的关联。如下图所示:
现在我们来学习 Dep 类,先看源码,它在文件 /src/core/observer/dep.js 种定义
/* @flow */
import type Watcher from './watcher'
import { remove } from '../util/index'
import config from '../config'
let uid = 0
/**
* A dep is an observable that can have multiple
* directives subscribing to it.
*/
export default class Dep {
// 静态变量,保存 Watcher 类型对象
static target: ?Watcher;
id: number; // 对象的ID
subs: Array<Watcher>; // 订阅者数组 元素即 Watcher对象
constructor () {
this.id = uid++
this.subs = []
}
// 添加订阅者
addSub (sub: Watcher) {
this.subs.push(sub)
}
// 删除订阅者
removeSub (sub: Watcher) {
remove(this.subs, sub)
}
// 依赖收集
depend () {
if (Dep.target) {
Dep.target.addDep(this)
}
}
// 通知订阅者 更新事件
notify () {
// stabilize the subscriber list first
const subs = this.subs.slice()
if (process.env.NODE_ENV !== 'production' && !config.async) {
// subs aren't sorted in scheduler if not running async
// we need to sort them now to make sure they fire in correct
// order
subs.sort((a, b) => a.id - b.id)
}
for (let i = 0, l = subs.length; i < l; i++) {
subs[i].update()
}
}
}
// The current target watcher being evaluated.
// This is globally unique because only one watcher
// can be evaluated at a time.
Dep.target = null // 全局静态变量初始赋值
const targetStack = [] // 定义 Watcher 数组,用于实现 栈结构
// 入栈 Watcher 对象
export function pushTarget (target: ?Watcher) {
targetStack.push(target)
Dep.target = target
}
// 出栈 Watcher 对象
export function popTarget () {
targetStack.pop()
Dep.target = targetStack[targetStack.length - 1]
}
Dep 除了成员变量和方法外,还定义了一个全局唯一用于存取 Watcher 对象的静态变量 Dep.target 以及一个用于实现栈结构的数组 targetStack。还有两个全局函数 pushTarget 和 popTarget,pushTarget 的作用是往 targetStack 数组尾部压入(保存) Watcher 对象,并把对象赋值给 Dep.target;popTarget 的作用是把 targetStack 数组尾部最后一个 Watcher 对象弹出(删除),然后把数组最后一个对象赋值给 Dep.target。
如果熟悉汇编语言我们就很容易理解,Vue 中这个 pushTarget 和 popTarget 其实很类似于汇编中的 push 和 pop 指令,还有 call 和 ret 指令也是隐式使用了出入栈,他们都是利用栈来临时保存关键信息以便必要时快捷地恢复。当调用 pushTarget 传入值时,Dep.target 用于依赖收集,当调用 pushTarget 传入空值时,依赖收集将被禁用。popTarget 则用于恢复原来的 Dep.target 状态,这个我们后面会继续研究学习。
再来看看 Dep 的成员方法,它们都比较简单。一个 addSub 用于添加依赖此变量的 Watcher 对象,把它们存入成员数组变量 subs 中。一个 removeSub 用于删除依赖此变量的 Watcher 对象,即从成员数组变量 subs 中删除特定 Watcher 对象。
方法 depend 是一个用于依赖收集的方法,它在响应式属性被引用时执行,即在属性的 reactiveGetter\reactiveSetter 方法中执行。它会判断如果存在 Dep.target 全局唯一静态变量(值为 Watcher 对象)那么就调用 Dep.target 的 addDep 方法,参数为本 Dep 对象,Dep.target 就是 Watcher 对象,所以 Dep.target.addDep(this) 可以简单理解为 watcher.addDep(dep)。
最后是 notify 方法,它用来通知订阅的所有 Watcher 对象,即调用 addSub 时添加的对象。这是通过变量数组 subs 调用每个元素的 update 方法实现通知的,具体我们在研究学习 Watcher 类和章节串联时具体再看。
Dep 类对于整个响应式功能模块是非常重要的,它与要实现响应式功能的数据对象和对象属性关联,它只在 Observer 类构造函数和 defineReactive 函数中实例化,并在 setter\getter 方法中的特定场景下执行依赖收集(执行 Dep.depend 方法)和派发更新通知(执行 Dep.notify 方法)。Dep 类代码量不大但很重要,它与其他类和模块配合完成响应式的功能,后面我们还会继续研究学习它。