Vue源码解析16--core.observer.watcher-Watcher类

Watcher类

watcher负责解析表达式,收集依赖,并在表达式的值改变时触发回调。

它能被用在vm.$watch()或是指令系统。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
let uid = 0

export default class Watcher {
// 观察的vm实例
vm: Component
// 表达式
expression: string
// 回调
cb: Function
// 这个watcher的id
id: number
// 指示是否为深度模式
deep: boolean
// 指示是否为用户创建的Watcher
user: boolean
// 指示是否为computedWatcher
computed: boolean
// 指示是否为同步模式
sync: boolean
// 指示值是否已经发生改变,要执行脏值检查
dirty: boolean
// 指示当前watcher是否还有效
active: boolean
// computedWatcher的Dep实例,做依赖收集
dep: Dep
// 非computedWatcher会订阅多个dep来做依赖收集,记录对它们的引用
deps: Array<Dep>
newDeps: Array<Dep>
depIds: SimpleSet
newDepIds: SimpleSet

before: ?Function
getter: Function
value: any

constructor(
vm: Component,
expOrFn: string | Function,
cb: Function,
options?: ?Object,
isRenderWatcher?: boolean,
) {
this.vm = vm
// RenderWatcher是组件用于模板渲染的watcher,也就是组件本身的_watcher
if (isRenderWatcher) {
vm._watcher = this
}
// 将watcher的引用加入vm._watchers
vm._watchers.push(this)

// options
if (options) {
this.deep = !!options.deep
this.user = !!options.user
this.computed = !!options.computed
this.sync = !!options.sync
this.before = options.before
} else {
this.deep = this.user = this.computed = this.sync = false
}

this.cb = cb
this.id = ++uid
this.active = true
// computed类型的watcher,需要首次触发,所以根据computed来设置dirty的初始状态
this.dirty = this.computed
this.deps = []
this.newDeps = []
this.depIds = new Set()
this.newDepIds = new Set()
this.expression = process.env.NODE_ENV !== 'production'
? expOrFn.toString()
: ''

// 解析getter的表达式
if (typeof expOrFn === 'function') {
this.getter = expOrFn
} else {
/**
* 针对string描述的getter,转换为访问传入Object中的对应键
* 比如:'test1.test2'对应取出target.test1.test2属性的值
*/
this.getter = parsePath(expOrFn)
if (!this.getter) {
this.getter = function () {
}
process.env.NODE_ENV !== 'production' && warn(
`Failed watching path: "${expOrFn}" ` +
'Watcher only accepts simple dot-delimited paths. ' +
'For full control, use a function instead.',
vm,
)
}
}

// computed类型根据dep来取值,普通类型用get()取值即可
if (this.computed) {
this.value = undefined
this.dep = new Dep()
} else {
this.value = this.get()
}
}
}

get()方法

获得getter的值,并重新收集依赖

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
Watcher.prototype.get = function() {
// 将前一个watcher入栈,当前watcher设置为正在运行
pushTarget(this)
let value
const vm = this.vm

try {
/**
* getter的this指针和参数都是vm
* this指针针对用户定制的getter中,this的指向,使得在用户在getter中能操作this
* 参数vm针对字符串表示的getter中,对它进行键访问
*/
value = this.getter.call(vm, vm)
} catch (e) {
if (this.user) {
handleError(e, vm, `getter for watcher "${this.expression}"`)
} else {
throw e
}
} finally {
// deep模式下,触发每个成员的依赖,追踪变化
if (this.deep) {
traverse(value)
}

// 当前watcher操作完毕,丢弃该watcher,取出前一个watcher
popTarget()
// 清理依赖收集
this.cleanupDeps()
}
return value
}

cleanUpDeps()方法

清理依赖收集

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
Watcher.prototype.cleanupDeps=  function() {
let i = this.deps.length
// 针对所有dep,移除dep.subs对该watcher的引用
while (i--) {
const dep = this.deps[i]
if (!this.newDepIds.has(dep.id)) {
dep.removeSub(this)
}
}
/**
* 用newDeps替代deps
*/
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
}

addDep方法

Deps中添加一个依赖。先暂存入newDeps做查重,没有重复的情况下,将该watcher放入dep.subs订阅者中

1
2
3
4
5
6
7
8
9
10
Watcher.prototype.addDep = function(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)
}
}
}

update()方法

订阅者的接口。当依赖发生改变时,会被调用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
Watcher.prototype.update = function() {
// computed的处理
if (this.computed) {
/**
* computed属性的watcher有两种模式:lazy和activated。
* 默认为lazy模式。
* 只有在它被至少一个其他的订阅者(通常是另一个computed属性或者组件的render函数)依赖时,才会变成activated模式。
* 所以可以根据订阅者的数量来判断为哪个模式。
*/
if (this.dep.subs.length === 0) {
/**
* 在lazy模式下,我们只希望在必要的时候才触发计算,因此将watcher标志为dirty,进行指示。
* 真正的计算会在computed属性被访问时,其getter触发的watcher.evaluate()方法中执行。
*/
this.dirty = true
} else {
/**
* 在activated模式下,我们要主动地执行计算,但是只在值发生变化的时候通知订阅者
*/
this.getAndInvoke(() => {
this.dep.notify()
})
}
}
// 同步(sync)模式下,直接渲染视图
else if (this.sync) {
this.run()
}
// 异步模式下,推送到队列中,下一个tick中调用
else {
queueWatcher(this)
}
}

run()方法

调度工作接口。通常由Scheduler调用,执行这个Watcher的更新。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
Watcher.prototype.run = function() {
if (this.active) {
this.getAndInvoke(this.cb)
}
}

Watcher.prototype.getAndInvoke = function(cb: Function) {
const value = this.get()
if (
value !== this.value ||
/**
* 对于deep模式的watcher,或是Object/Array的watcher,
* value !== this.value只能判断出它的引用没有发生改变,但是成员的值可能已经发生变化了
*/
isObject(value) ||
this.deep
) {
// 设置新的值
const oldValue = this.value
this.value = value
this.dirty = false
/**
* 给callback绑定this为vm,传参执行
* 这一段决定了callback的形式为 (value, oldValue) => void(0),
* 并且可以安全地访问this为当前vm
*/
if (this.user) {
try {
cb.call(this.vm, value, oldValue)
} catch (e) {
handleError(e, this.vm, `callback for watcher "${this.expression}"`)
}
} else {
cb.call(this.vm, value, oldValue)
}
}
}

evaluate()方法

检查dirty,返回watcher的最新值。只会被computed属性的watcher调用。

1
2
3
4
5
6
7
Watcher.prototype.evaluate = function() {
if (this.dirty) {
this.value = this.get()
this.dirty = false
}
return this.value
}

depend()方法

dep中添加这个watcher依赖,只会被computed属性的watcher调用。

1
2
3
4
5
Watcher.prototype.depend = function() {
if (this.dep && Dep.target) {
this.dep.depend()
}
}

teardown()方法

停用这个watcher,清除这个watcher的所有引用,包括vm._watchersdep.subs中的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Watcher.prototype.teardown = function() {
if (this.active) {
/**
* 这是一个高成本操作。
* 如果是因为vm要摧毁了,所以要停用这个watcher,那就无需去vm._watchers中做清除
*/
if (!this.vm._isBeingDestroyed) {
remove(this.vm._watchers, this)
}
// 清除dep.subs中对它的引用
let i = this.deps.length
while (i--) {
this.deps[i].removeSub(this)
}
// 设置为inactive,停用
this.active = false
}
}

Vue源码解析15--core.observer.dep-Dep类

Dep类

Dep类,负责依赖收集和发布更新通知,可以让多个watcher去订阅它。它记录了所有订阅它的watcher,放在subs中。其静态属性Dep.target用于指示当前正在执行的watcher。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
let uid = 0

/**
* dep是一个可以让多个指令去订阅它的观察者
*/
export default class Dep {
static target: ?Watcher
id: number
subs: Array<Watcher>

constructor() {
this.id = uid++
this.subs = []
}

// 添加一个Watcher
addSub(sub: Watcher) {
this.subs.push(sub)
}

// 移除一个Watcher
removeSub(sub: Watcher) {
remove(this.subs, sub)
}

// 依赖收集,当存在Dep.target的时候添加观察者对象
depend() {
if (Dep.target) {
Dep.target.addDep(this)
}
}

// 通知所有订阅者,发生了update
notify() {
// 保证subs为Array
const subs = this.subs.slice()
for (let i = 0, l = subs.length; i < l; i++) {
subs[i].update()
}
}
}

/**
* Dep.target指示当前正在做检查的watcher。
* 它是一个静态属性,在全局是独一无二的,因为同一时间只会有一个watcher在做检查
*/
Dep.target = null
const targetStack = []

// push一个Watcher进入targetStack,Dep.target记录最新的一条记录
export function pushTarget(_target: ?Watcher) {
if (Dep.target) targetStack.push(Dep.target)
Dep.target = _target
}

// pop顶层Watcher退出targetStack,提供给Dep.target
export function popTarget() {
Dep.target = targetStack.pop()
}

Vue源码解析14--core.observer.index-Observer类

Observer类

Observer类,附着到每个被观察的对象。

一旦被添加,observer会将目标对象的属性转换为getter/setters形式,其dep用于收集依赖和发出更新通知。

它包括了对以下数据的处理:

  • 基本类型,数组,对每一个成员加入观察。同时在数组的原型上,对数组的部分原生方法进行修改(push,pop等),从而让它们在调用时发出更新通知。
  • 对象,直接对键进行遍历并绑定。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
/**
* 用于指示是否要进行观察
* 有时候在component的update运算中,要停止观察
*/
export let shouldObserve: boolean = true

export function toggleObserving(value: boolean) {
shouldObserve = value
}

export class Observer {
value: any
dep: Dep
// 将这个Object作为根$data的vm数量
vmCount: number

constructor(value: any) {
this.value = value
this.dep = new Dep()
this.vmCount = 0
// 将这个实例作为目标对象的__ob__属性
def(value, '__ob__', this)

if (Array.isArray(value)) {
/**
* 如果是数组,对数组的部分原生方法进行修改,从而当它们被调用时,会发出更新的通知。
* 如果当前浏览器支持__proto__属性,则直接覆盖当前数组对象原型上的原生数组方法,如果不支持该属性,则直接覆盖数组对象的原型。
*/
const augment = hasProto
? protoAugment
: copyAugment
augment(value, arrayMethods, arrayKeys)
this.observeArray(value)
} else {
/**
* 如果是对象,直接进行遍历绑定
*/
this.walk(value)
}
}

/**
* 遍历每个属性,将他们转换成getter/setter形式。
* 这个方法只应在value的类型为Object的时候调用
*/
walk(obj: Object) {
const keys = Object.keys(obj)
for (let i = 0; i < keys.length; i++) {
defineReactive(obj, keys[i])
}
}

/**
* 将一个Array的每个item加入观察
*/
observeArray(items: Array<any>) {
for (let i = 0, l = items.length; i < l; i++) {
observe(items[i])
}
}
}

/**
* 如果支持__proto__属性,直接将target的__proto__替换为src
*/
function protoAugment(target, src: Object, keys: any) {
target.__proto__ = src
}

/**
* 如果不支持__proto__属性,遍历src的每个键值对,混入到target上去
*/
function copyAugment(target: Object, src: Object, keys: Array<string>) {
for (let i = 0, l = keys.length; i < l; i++) {
const key = keys[i]
def(target, key, src[key])
}
}

Observe函数

尝试给一个对象创建observer实例,挂载在vm.__ob__。如果成功创建,返回新的observer,如果该对象已经有observer,返回原observer。

创建的条件:

  1. 必须是普通对象或Array,可以添加新属性(没有被Object.preventExtension()处理过)
  2. shouldObserve为true
  3. 不是服务端渲染(SSR)
  4. 不是Vue实例(Vue实例要结合instance的创建过程去达到响应式)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
export function observe(value: any, asRootData: ?boolean): Observer | void {
if (!isObject(value) || value instanceof VNode) {
return
}
let ob: Observer | void
if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) {
ob = value.__ob__
} else if (
shouldObserve &&
!isServerRendering() &&
(Array.isArray(value) || isPlainObject(value)) &&
Object.isExtensible(value) &&
!value._isVue
) {
ob = new Observer(value)
}
if (asRootData && ob) {
ob.vmCount++
}
return ob
}

defineReactive函数

用于为对象定义一个响应式属性。这个被定义的属性会获得一个内置的Dep,用于收集它的依赖,并发布它的更新通知。

Observer中value的每个属性,以及Vue.set()添加的新属性都会经历它的改造

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
export function defineReactive(obj: Object, key: string, val: any, customSetter?: ?Function, shallow?: boolean) {
// 给这个属性提供一个内置的Dep
const dep = new Dep()

// 检查PropertyDescriptor的configurable
const property = Object.getOwnPropertyDescriptor(obj, key)
if (property && property.configurable === false) {
return
}

// 取出它原有的getter和setter,在此基础上打patch
const getter = property && property.get
const setter = property && property.set
if ((!getter || setter) && arguments.length === 2) {
val = obj[key]
}

// 深度模式下,给这个属性也添加observer
let childOb = !shallow && observe(val)

Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
/**
* 先执行原本的getter,然后收集依赖
*/
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
},
/**
* 先执行原本的getter,检查新的值是否发生了改变。
* 然后执行原本的setter,最后发布通知
*/
set: function reactiveSetter(newVal) {
const value = getter ? getter.call(obj) : val
if (newVal === value || (newVal !== newVal && value !== value)) {
return
}
if (process.env.NODE_ENV !== 'production' && customSetter) {
customSetter()
}
if (setter) {
setter.call(obj, newVal)
} else {
val = newVal
}
childOb = !shallow && observe(newVal)
dep.notify()
},
})
}

Vue.prototype.set()

Vue.set和vm.$set的实现,在target上设置一个响应式的属性。

如果是新增的属性,使用defineReactive()来执行添加,并立即触发一次通知。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
export function set(target: Array<any> | Object, key: any, val: any): any {
if (process.env.NODE_ENV !== 'production' && (isUndef(target) || isPrimitive(target))) {
warn(`Cannot set reactive property on undefined, null, or primitive value: ${(target: any)}`)
}

/**
* Array用splice()方法插入新的值,注意Array.prototype.splice已经被改造过了,会触发一次更新通知
*/
if (Array.isArray(target) && isValidArrayIndex(key)) {
target.length = Math.max(target.length, key)
target.splice(key, 1, val)
return val
}
/**
* 对于已经存在的实例属性,直接赋值,会触发setter
*/
if (key in target && !(key in Object.prototype)) {
target[key] = val
return val
}
const ob = (target: any).__ob__
if (target._isVue || (ob && ob.vmCount)) {
process.env.NODE_ENV !== 'production' && warn(
'Avoid adding reactive properties to a Vue instance or its root $data ' +
'at runtime - declare it upfront in the data option.',
)
return val
}
// 没有observer的,直接返回
if (!ob) {
target[key] = val
return val
}
/**
* 新增的属性,使用defineReactive()来实现响应式,并立即通知一次更新
*/
defineReactive(ob.value, key, val)
ob.dep.notify()
return val
}

Vue.prototype.delete

Vue.delete和vm.$delete的实现。删除某个属性,并且触发更新通知。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
/**
*
*
*/
export function del(target: Array<any> | Object, key: any) {
if (process.env.NODE_ENV !== 'production' &&
(isUndef(target) || isPrimitive(target))
) {
warn(`Cannot delete reactive property on undefined, null, or primitive value: ${(target: any)}`)
}

/**
* Array用splice()删除这个成员,注意Array.prototype.splice已经被改造过了,会触发一次更新通知
*/
if (Array.isArray(target) && isValidArrayIndex(key)) {
target.splice(key, 1)
return
}
const ob = (target: any).__ob__
if (target._isVue || (ob && ob.vmCount)) {
process.env.NODE_ENV !== 'production' && warn(
'Avoid deleting properties on a Vue instance or its root $data ' +
'- just set it to null.',
)
return
}
if (!hasOwn(target, key)) {
return
}
/**
* 删除这个属性,如果target存在observer,那么需要触发一次更新通知
*/
delete target[key]
if (!ob) {
return
}
ob.dep.notify()
}