这是我参与11月更文挑战的第6天,活动详情查看:2021最后一次更文挑战
前言
上篇已经看了Watcher构造函数的实现部分,这篇来具体看下Watcher内部定义的几个函数。
首先看下与Dep相关的两个函数,addDep与update
addDep
在向Dep添加依赖时会调用depend方法,其内部是调用了Watcher的addDep。
depend () {
if (Dep.target) {
Dep.target.addDep(this)
}
}
看一下addDep方法:
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)
}
}
}
addDep函数主要做了两件事:
- 把要订阅的Dep添加到newDeps数组中,并且把其id添加到newDepIds中
- 把当前Watcher依赖添加到dep中
添加前都需要先判断是否已经被添加过,避免重复添加
update
在依赖更新时会触发Dep的notify方法。其内部调用了Watcher的update方法。
notify () {
...
for (let i = 0, l = subs.length; i < l; i++) {
subs[i].update()
}
}
}
看一下Watcher类的update函数:
update () {
/* istanbul ignore else */
if (this.lazy) {
this.dirty = true
} else if (this.sync) {
this.run()
} else {
queueWatcher(this)
}
}
-
首先判读lazy值,如果为true,说明是computed Watcher,将dirty设置为true,不计算。
-
接下来判断sync值,如果为true,说明要同步计算,在当前tick执行watcher的回调。
-
否则将当前Watcher push到Watcher队列中。queueWatcher函数后边再具体来看。
run
看一下run函数的具体实现:
run () {
if (this.active) {
const value = this.get()
if (
value !== this.value ||
// Deep watchers and watchers on Object/Arrays should fire even
// when the value is the same, because the value may
// have mutated.
isObject(value) ||
this.deep
) {
// set new value
const oldValue = this.value
this.value = value
if (this.user) {
try {
this.cb.call(this.vm, value, oldValue)
} catch (e) {
handleError(e, this.vm, `callback for watcher "${this.expression}"`)
}
} else {
this.cb.call(this.vm, value, oldValue)
}
}
}
}
- 首先判断active的值,确保当前Watcher还存活着
- 接下来调用get拿到Watcher当前最新的value值
- 接下来判断是否满足执行条件(value!==this.value || isObject(value) || this.deep),如果满足则执行cb回调函数。
depend
depend函数用来将当前Watcher添加到所有它要依赖的dep中。
depend () {
let i = this.deps.length
while (i--) {
this.deps[i].depend()
}
}
teardown
teardown函数与depend函数相对,用来把当前Watcher从所有它订阅的dep中移除。
teardown () {
if (this.active) {
// remove self from vm's watcher list
// this is a somewhat expensive operation so we skip it
// if the vm is being destroyed.
if (!this.vm._isBeingDestroyed) {
remove(this.vm._watchers, this)
}
let i = this.deps.length
while (i--) {
this.deps[i].removeSub(this)
}
this.active = false
}
}
}
evaluate
evaluate () {
this.value = this.get()
this.dirty = false
}
evaluate函数主要是用来被computed Watcher调用。
computed Watcher在初始化时并没有求值,是直接赋值为undefined,并且把dirty属性置为true。
当computed Watcher真正需要被求值时,会调用evaluate函数,同时把dirty置为false(说明此时数据已经干净了,是最新的)。
Dep与Watcher
看到这里我们已经可以看出,Dep与Watcher是多对多的关系,Dep的subs数组中保存了收集到的Watcher依赖,而Watcher的deps数组中也保存了自己去订阅的Dep实例。
对象属性在被Object.defineProperty拦截时,每个属性都会有一个自己的Dep实例,里边保存了该属性所有相关的Watcher。
整体来回顾下这个流程:Watcher在取值的时候(get函数)调用pushTarget把全局变量Dep.target置为当前Watcher;然后在调用响应式对象的get拦截时会有一个dep.target判断,如果不为空,则把这个Dep实例添加到该Watcher的newDeps数组中;并且同时判断该dep是否将此Watcher添加到它自己的subs数组中,如果没有添加则将该Watcher添加到Dep实例的subs中。
// 1. pushTarget当前Watcher,src/core/observer/watcher.js
class Watcher{
get () {
pushTarget(this)
}
}
// 2. 将Dep.target置为当前Watcher,src/core/observer/dep.js
function pushTarget(target) {
targetStack.push(target)
Dep.target = target
}
// 3. 响应式对象的get触发依赖收集,src/core/observer/index.js
function defineReactive(obj, key, val, customSetter, shallow) {
Object.defineProperty(obj, key,{
get: function reactiveGetter() {
if (Dep.target) {
dep.depend()
}
}
})
}
// 4. 调用Watcher对象的addDep方法并传入当前的Dep实例,src/core/observer/dep.js
class Dep {
depend () {
if (Dep.target) {
Dep.target.addDep(this)
}
}
}
// 5. 将当前Dep实例dep添加到对应的Watcher实例中的newDeps数组中,同时调用Dep的addSub方法。src/core/observer/watcher.js
class Watcher {
addDep (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)
}
}
}
}
// 6. addSub将该Watcher添加到对应的Dep的subs数组中,src/core/observer/dep.js
class Dep {
addSub (sub) {
this.subs.push(sub)
}
}
看一个简单的例子:
<template>
<div>
<span>{{firstName}}</span>
<p>{{userName}}</p>
</div>
</template>
<script>
export default {
data() {
return{
firstName: 'first',
lastName: 'last'
}
},
computed: {
userName() {
return this.firstName + this.lastName
}
},
watch: {
'userName'(newVal){
console.log(newVal)
}
}
}
</script>
看一下Watcher列表:
这里包含三个Watcher:
- computed Watcher(expression: "function userName() { return this.firstName + '-' + this.lastName; }")
- user Watcher(expression: "userName")
- render Watcher(expression: "function (){vm._update(vm._render(), hydrating); }")
看一下Dep(1209)与Dep(1210):
总体来说,我觉得可以这么理解这个多对多的关系: