Vue源码解析19--core.observer.traverse-Watch的深度模式

Traverse

在Watcher设置为deep: true的模式下使用。

递归每一个对象或数组,触发他们转换过的getter,这样每个成员都会被依赖收集,形成深度的依赖关系。

使用一个Set是为了记录已经处理过的depId,避免重复触发某个dep

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
const seenObjects = new Set()

export function traverse (val: any) {
_traverse(val, seenObjects)
seenObjects.clear()
}

function _traverse (val: any, seen: SimpleSet) {
let i, keys
const isA = Array.isArray(val)
if ((!isA && !isObject(val)) || Object.isFrozen(val) || val instanceof VNode) {
return
}

// 记录处理depId,跳过已经处理过的
if (val.__ob__) {
const depId = val.__ob__.dep.id
if (seen.has(depId)) {
return
}
seen.add(depId)
}

/**
* 遍历每个成员,递归处理,从而对成员为引用类型的情况做深度处理。
* 注意在传参时使用了键访问,此时便会触发getter。
*/
if (isA) {
i = val.length
while (i--) _traverse(val[i], seen)
} else {
keys = Object.keys(val)
i = keys.length
while (i--) _traverse(val[keys[i]], seen)
}
}

Vue源码解析18--core.observer.array-对Array的patch

observer.array

Array的以下方法会改变原本的对象,所以需要对其进行patch处理,改造为响应式执行:

  • push
  • pop
  • shift
  • splice
  • sort
  • reverse

本质上是在执行原有API的基础上,添加两个步骤:

  1. 如果有新元素进入,对新元素进行响应式改造
  2. 触发一次更新通知
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
const arrayProto = Array.prototype
export const arrayMethods = Object.create(arrayProto)

const methodsToPatch = ['push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse']

/**
* 拦截Array中会改变Array对象自身的方法,在它们调用时,触发事件
* 相当于对它做Monkey Patch
*/
methodsToPatch.forEach(function (method) {
// 暂存原生的方法
const original = arrayProto[method]
// 改造原生方法(将其PropertyDescriptor的value覆盖)
def(arrayMethods, method, function mutator(...args) {
const result = original.apply(this, args)
const ob = this.__ob__

// 处理有新元素插入的情况
let inserted
switch (method) {
case 'push':
case 'unshift':
inserted = args
break
case 'splice':
inserted = args.slice(2)
break
}
// 如果有新的元素插入,将插入的新元素纳入观察
if (inserted) ob.observeArray(inserted)

// 通知发生了改变
ob.dep.notify()
return result
})
})

Vue源码解析17--core.observer.scheduler-调度器

Scheduler调度器

Scheduler全局只有一个,所以不建立class。

它负责对Watcher中异步的更新做调度,在nextTick()中将其逐个执行。

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
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
// 一次执行的update次数不能超过100次,帮助停止死循环
export const MAX_UPDATE_COUNT = 100

// 当前队列
const queue: Array<Watcher> = []
// 当前正在使用的组件
const activatedChildren: Array<Component> = []
// 用于存放已有watcher的id,防止重复
let has: { [key: number]: ?true } = {}
// 记录每个watcher在一次执行中,update的次数。超过最大次数,说明存在死循环,要提示用户。
let circular: { [key: number]: number } = {}
let waiting = false
// 指示正在执行队列
let flushing = false
let index = 0

/**
* 重置Scheduler的状态
*/
function resetSchedulerState () {
index = queue.length = activatedChildren.length = 0
has = {}
if (process.env.NODE_ENV !== 'production') {
circular = {}
}
waiting = flushing = false
}

/**
* 在Vue.nextTick()中广泛使用,
* 在下个tick中,执行队列,调用watcher.run()来触发watcher的回调
*/
function flushSchedulerQueue () {
flushing = true
let watcher, id

/**
* 执行前,对队列进行排序
* 这将保证:
* 1. 父组件优先于子组件进行更新(父组件id较小)
* 2. 组件中,用户自定义的watcher优先于组件render的watcher执行(自定义watcher比render的watcher更早定义,id小)
* 3. 如果在父组件的watcher更新中,确认要摧毁子组件了,那就可以跳过子组件的watcher(性能优化)
*/
queue.sort((a, b) => a.id - b.id)

/**
* 这里不缓存length,因为在执行现有的watcher.run()中,队列可能会加入新的成员
*/
for (index = 0; index < queue.length; index++) {
watcher = queue[index]
if (watcher.before) {
watcher.before()
}
id = watcher.id
has[id] = null
watcher.run()
// dev模式下,帮助检查和调停环形更新(死循环)
if (process.env.NODE_ENV !== 'production' && has[id] != null) {
circular[id] = (circular[id] || 0) + 1
if (circular[id] > MAX_UPDATE_COUNT) {
warn(
'You may have an infinite update loop ' + (
watcher.user
? `in watcher with expression "${watcher.expression}"`
: `in a component render function.`
),
watcher.vm
)
break
}
}
}

// 在重置前,保存之前队列的副本
const activatedQueue = activatedChildren.slice()
const updatedQueue = queue.slice()
resetSchedulerState()

// 调用组件的activated和updated钩子
callActivatedHooks(activatedQueue)
callUpdatedHooks(updatedQueue)

// devtool的钩子
if (devtools && config.devtools) {
devtools.emit('flush')
}
}

// 调用updated钩子
function callUpdatedHooks (queue) {
let i = queue.length
while (i--) {
const watcher = queue[i]
const vm = watcher.vm
if (vm._watcher === watcher && vm._isMounted) {
callHook(vm, 'updated')
}
}
}

/**
* 在patch期间被激活(activated)的keep-alive组件保存在队列中。
* patch结束后该队列会被处理
*/
export function queueActivatedComponent (vm: Component) {
// 将vm._inactive设为false,render函数就可以依赖于watcher检查
vm._inactive = false
activatedChildren.push(vm)
}

// 调用activated钩子,并将其子组件activate化
function callActivatedHooks (queue) {
for (let i = 0; i < queue.length; i++) {
queue[i]._inactive = true
activateChildComponent(queue[i], true /* true */)
}
}

/**
* 将一个watcher放入队列。
* 如果新放入的watcher和现有的有重复(根据id判断),那就跳过,除非是在flush阶段放入的
* @param watcher
*/
export function queueWatcher (watcher: Watcher) {
const id = watcher.id
if (has[id] == null) {
has[id] = true
if (!flushing) {
queue.push(watcher)
} else {
/**
* 如果已经在flush了,根据它的id,找到队列中合适的位置进行插入
* 如果已经运行到它后面的id了,插在队列首部,下一个立即执行
*/
let i = queue.length - 1
while (i > index && queue[i].id > watcher.id) {
i--
}
queue.splice(i + 1, 0, watcher)
}
// 安排flush
if (!waiting) {
waiting = true
nextTick(flushSchedulerQueue)
}
}
}