Vue 源码浅析:数据动态响应- watcher 和 dep 观察监听机制


Vue 源码浅析:数据动态响应- watcher 和 dep 观察监听机制


依赖订阅器 Dep

下面是 Dep 类方法的代码:

<code>export default class Dep {
static target: ?Watcher;
id: number;
subs: Array<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()
for (let i = 0, l = subs.length; i < l; i++) {
subs[i].update()
}
}
}/<watcher>/<code>

能看到 Dep 类函数中的代码是典型的的观察者模型

通过 dependnotify 方法,让 Watcher Dep 建立起互相观察的关系。

对于全局,又设立了当前监听目标对象 Dep.target 及监听队列 targetStack

<code>Dep.target = null
const targetStack = []

export function pushTarget (target: ?Watcher) {
targetStack.push(target)
Dep.target = target
}

export function popTarget () {
targetStack.pop()
Dep.target = targetStack[targetStack.length - 1]
}/<code>

哪里对 Dep target 赋值了?

我们在 defineReactive 中的对象属性访问器 get 方法中看到了 Dep.target 的判断。到底哪里设置列 Dep.target

尝试在 initData 翻了遍,也只找到如下代码:

<code>function initData (vm: Component) {
let data = vm.$options.data
data = vm._data = typeof data === 'function'
? getData(data, vm)
: data || {}
//...
}

export function getData (data: Function, vm: Component): any {
// #7573 disable dep collection when invoking data getters
pushTarget()
//...
}/<code>

只有 pushTarget 方法会往监听队列添加 Watcher 对象:

<code>export function pushTarget (target: ?Watcher) {
targetStack.push(target)
Dep.target = target
}

export function popTarget () {
targetStack.pop()
Dep.target = targetStack[targetStack.length - 1]
}/<code>

但此处的 pushTarget 的入参是个空值,但实际走到此判断时,Dep.target 是有值的,那到底哪里设置的?

Vue 源码浅析:数据动态响应- watcher 和 dep 观察监听机制


其实是在 beforeMount 触发完后,会有 Watcher 对象的创建,它会监听我们模板上相关数据的变动:

<code>callHook(vm, 'beforeMount')

updateComponent = () => {
vm._update(vm._render(), hydrating)
}

new Watcher(vm, updateComponent, noop, {
before () {
if (vm._isMounted && !vm._isDestroyed) {
callHook(vm, 'beforeUpdate')
}
}
}, true /* isRenderWatcher */)

//.../<code>

这里先简单提下:Watcher 构造器初始化时,会调用内部的 get 方法,此处会有调用 pushTarget 方法,内部会往监听队列中添加 Watcher 对象,再执行 this.getter,从而触发我们 defineReactive 中的 get

方法,解析相关数据变量。

<code>get () {
pushTarget(this)
let value
const vm = this.vm
try {
value = this.getter.call(vm, vm)
}
//...
}/<code>

能看到这里的 this.getter 就是 updateComponent 方法:

Vue 源码浅析:数据动态响应- watcher 和 dep 观察监听机制


depend 和 addSub 方法

大致流程是:通过 defineReactive 中的对象属性访问器 getter 触发 dep.depend 方法,通知 watcher 对象添加依赖 dep,如果不存在对应依赖,则会往 dep 监听队列 subs 添加当前 watcher 对象。

<code>depend () {
if (Dep.target) {
Dep.target.addDep(this)
}
}/<code>

addDep Watcher 函数中的方法,会往 newDepIds newDeps 设置相关 Dep 对象信息:

<code>addDep (dep: Dep) {
const id = dep.id
if (!this.newDepIds.has(id)) {
this.newDepIds.add(id)
this.newDeps.push(dep)
if (!this.depIds.has(id)) {
dep.addSub(this)
}
}

}/<code>

如果依赖收集 depIds Set 集合中不包含当前依赖对象的 id(dep.id),则会通过 dep.addSub() 往监听队列 dep.subs 添加 Watcher 对象:

<code>addSub (sub: Watcher) {
this.subs.push(sub)
}/<code>

notify 和 removeSub

当数据发生更新时,会触发 defineReactive 中的对象属性访问器 setter 方法,最后触发 dep.notify()

<code>notify () {
// stabilize the subscriber list first
const subs = this.subs.slice()
for (let i = 0, l = subs.length; i < l; i++) {
subs[i].update()
}
}/<code>

update Watcher 中的方法,内部根据 sync 调用 run 或者 queueWatcher 方法,从而实现数据更新渲染,对应我们到 Watcher 对象再细看:

<code>update () {
/* istanbul ignore else */
if (this.lazy) {
this.dirty = true
} else if (this.sync) {
this.run()
} else {
queueWatcher(this)
}
}/<code>

监听者 Watcher

我们已经知道 Dep.target 是哪里赋值的(即:哪里创建了 Watcher 对象的实例):

<code>callHook(vm, 'beforeMount')

updateComponent = () => {
vm._update(vm._render(), hydrating)
}

new Watcher(vm, updateComponent, noop, {
before () {
if (vm._isMounted && !vm._isDestroyed) {
callHook(vm, 'beforeUpdate')
}
}
}, true /* isRenderWatcher */)

//.../<code>

那么接下来到 Watcher 类方法中,看怎么做了什么?

构造器初始化的工作

那么详细看下 Watcher 构造器做了哪些事情:

<code>constructor ( 

vm: Component,
expOrFn: string | Function,
cb: Function,
options?: ?Object,
isRenderWatcher?: boolean
) {
//...
this.cb = cb
//...
this.deps = []
this.newDeps = []
this.depIds = new Set()
this.newDepIds = new Set()
this.expression = process.env.NODE_ENV !== 'production'
? expOrFn.toString()
: ''
// parse expression for getter
if (typeof expOrFn === 'function') {
this.getter = expOrFn
} else {
//...
}
this.value = this.lazy
? undefined
: this.get()
}/<code>

中间省略了一些对象属性,重点放在依赖相关的数据结构中,同时还有对象内部的 getter 和 value 属性的赋值过程。

定义依赖相关的数据结构

<code>this.deps = []
this.newDeps = []
this.depIds = new Set()
this.newDepIds = new Set()/<code>

这四个依赖相关的数据结构在数据观察中其中桥梁的作用,并且他们两两一对,有着新老值的含义。后续对应方法细看。

初始化 watcher.getter 方法

解析 expOrFn 入参(如果了解模板渲染相关代码,此处的 expOrFn 将是一个 render 相关的方法,以用于渲染 vnode 虚拟节点),将其表达式赋值给 Watcher 对象的 this.getter 属性:

<code>this.expression = process.env.NODE_ENV !== 'production'
? expOrFn.toString()
: ''
if (typeof expOrFn === 'function') {
this.getter = expOrFn
} else {
this.getter = parsePath(expOrFn)
}/<code>

主流程中的 Watcher 对象创建的表达式是个 function 函数:

Vue 源码浅析:数据动态响应- watcher 和 dep 观察监听机制


初始化 watcher.value 属性

Watcher 对象创建后,将通过 this.get() 方法获取 this.value 的初始值:

<code>this.value = this.lazy
? undefined
: this.get()/<code>

执行 watcher.getter,更新依赖关系

我们进入 get 方法,看它做了什么事:

<code>get () {
pushTarget(this)
let value
const vm = this.vm
try {
value = this.getter.call(vm, vm)
}catch (e) {
//...
} finally {
popTarget()
this.cleanupDeps()
}
return value
}/<code>

它就是执行一次我们前面设置的 this.getter

方法(expOrFn 表达式),从而得到了节点结果。下面进入 get 方法内,细看:

添加当前监听目标 target

通过 pushTarget,添加全局当前监听对象及监听队列;popTarget 则是删除:

<code>Dep.target = null
const targetStack = []

export function pushTarget (target: ?Watcher) {
targetStack.push(target)
Dep.target = target
}

export function popTarget () {
targetStack.pop()
Dep.target = targetStack[targetStack.length - 1]
}/<code>

调用 getter 函数,触发数据响应

<code>value = this.getter.call(vm, vm)/<code>

我们已经知道 this.getter 就是 expOrFn 入参函数,当此函数被 call 后,其中的模板变量将触发 defineReactive 中的 getter 方法:

<code>Object.defineProperty(obj, key, { 

enumerable: true,
configurable: true,
get: function reactiveGetter () {
const value = getter ? getter.call(obj) : val
if (Dep.target) {
dep.depend()
if (childOb) {
childOb.dep.depend()
if (Array.isArray(value)) {
dependArray(value)
}
}
}
return value
},
//..
}/<code>

因为此时 Dep.target 已有值,则会通过 dep.depend 方法调用到 Watcher 对象中的 addDep 方法,往依赖相关变量设置新的数据结构:

<code>//source-code\\vue\\src\\core\\observer\\dep.js
depend () {
if (Dep.target) {
Dep.target.addDep(this)
}
}
addSub (sub: Watcher) {
this.subs.push(sub)
}/<code>
<code>//source-code\\vue\\src\\core\\observer\\watcher.js
addDep (dep: Dep) {
const id = dep.id
if (!this.newDepIds.has(id)) {
this.newDepIds.add(id)
this.newDeps.push(dep)
if (!this.depIds.has(id)) {
dep.addSub(this)

}
}
}/<code>

流程将是这样:

Dep.depend() --> Watcher.addDep() --> 更新 Watcher.newDepIds/newDeps,添加 Dep 对象及 DepId

--> Dep.addSub() --> 更新 Dep.subs,添加 Watcher 对象。

这就是 defineReactive getter 函数触发的流程。

清理依赖数据

this.getter 调用完后,最后通过 popTarget 移除当前监听 target 对象:

<code>finally{
\tpopTarget()
this.cleanupDeps()
}/<code>

再调用 cleanupDeps 方法,清理相关依赖对象:

<code>cleanupDeps () {
let i = this.deps.length
while (i--) {
const dep = this.deps[i]
if (!this.newDepIds.has(dep.id)) {
dep.removeSub(this)
}
}
let tmp = this.depIds
this.depIds = this.newDepIds
this.newDepIds = tmp
this.newDepIds.clear()
tmp = this.deps
this.deps = this.newDeps

this.newDeps = tmp
this.newDeps.length = 0
}/<code>

while 我们暂时跳过,先看后面逻辑。

将之前添加的依赖 Set 集合对象 newDepIds 赋值给 depIds,并将 newDepIds 清空。

同时,将依赖队列 newDeps 赋值给 deps,最后把 newDeps 清空。

这样把最新的依赖对象交给了 depIds 集合和 deps 队列;并清空了之前的依赖对象数据。

当后续再触发数据更新机制时,Watcher 对象上的 depIds,deps 将存在上次的依赖对象,这样我们再回看 while 逻辑:

发现 while 内部逻辑就是检查当前最新的 newDepIds 中,是否和之前的 depIds 中存在一样的 dep.id,排除重复多次依赖

数据动态响应的触发

当我们操作更新具有动态响应特性的数据时,会触发 defineReactive setter 函数,最后调用

dep.notify 方法:

<code>notify () {
//..
subs[i].update()
}/<code>

而 update 将根据不同的条件,选择不同的运行机制:

<code>update () {
/* istanbul ignore else */
if (this.lazy) {
this.dirty = true
} else if (this.sync) {
this.run()
} else {
queueWatcher(this)
}
}/<code>

不管怎样,最终都将执行到 run 方法:

<code>run () {
if (this.active) {
const value = this.get()
if (
value !== this.value || isObject(value) || this.deep
) {
// set new value
const oldValue = this.value
this.value = value
//...
this.cb.call(this.vm, value, oldValue)
}
}
}/<code>

能看到内部还会执行下 watcher.get 方法,获取 value 结果值,并更新视图。

如果我们专门设置了 watcher 方法,则将通过 this.cb 把新老 value 传入 cb 进行回调流程;

总结

上面就是大致数据动态响应的流程,详细说了从 observe 观察开始,创建 Observer 观察对象,通过 defineReactive 方法建立观察数据的"更新机制",内部通过依赖相关的数据结构,关联了 Dep 和 Watcher 方法,形成了观察者模型。


分享到:


相關文章: