上一小节分析了响应式数据依赖收集的过程,这一节接着分析当数据发生改变通知更新的过程,首先看下defineReactive
的Setter
部分,该方法定义在src/core/observer/index.js
export function defineReactive (
obj: Object,
key: string,
val: any,
customSetter?: ?Function,
shallow?: boolean
) {
const dep = new Dep()
const property = Object.getOwnPropertyDescriptor(obj, key)
if (property && property.configurable === false) {
return
}
// cater for pre-defined getter/setters
const getter = property && property.get
const setter = property && property.set
if ((!getter || setter) && arguments.length === 2) {
val = obj[key]
}
let childOb = !shallow && observe(val)
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: function reactiveGetter () {
// ......
},
set: function reactiveSetter (newVal) {
const value = getter ? getter.call(obj) : val
/* eslint-disable no-self-compare */
if (newVal === value || (newVal !== newVal && value !== value)) {
return
}
/* eslint-enable no-self-compare */
if (process.env.NODE_ENV !== 'production' && customSetter) {
customSetter()
}
// #7981: for accessor properties without setter
if (getter && !setter) return
if (setter) {
setter.call(obj, newVal)
} else {
val = newVal
}
childOb = !shallow && observe(newVal)
dep.notify()
}
})
}
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
当响应式对象的属性值发生变化时会触发对应的响应式Setter
方法,在方法内部会先调用预先在该属性定义的Getter
方法获取属性值value
,并将修改的值newVal
和value
做一个比对,如果value
和newVal
为相同的值或者value
和newVal
都为NaN
,则不会触发更新,接着会判断是否存在自定义customSetter
,如果存在则调用该方法,customSetter
后续小节会讲解到,如果只存在预先定义的Getter
而没有Setter
,相当于任何修改操作都无效,该属性为只读属性,始终返回的都是固定值,所以直接return
返回,如果存在预先定义的Setter
则调用,否则将修改的newVal
赋值给val
,接着是根据修改的值newVal
生成新的childOb
,这里其实用到了闭包的思想,就不细说了(只可意会不可言传了),最后调用响应式对象属性的dep.notify
方法实现派发更新
继续回到src/core/observer/dep.js
文件中,查看Dep
类中对notify
方法的实现
export default class Dep {
static target: ?Watcher;
id: number;
subs: Array<Watcher>;
constructor () {
this.id = uid++
this.subs = []
}
// ......
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()
}
}
}
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
在notify
方法中,会循环遍历响应式对象属性对应的dep.subs
中订阅的watcher
实例,并调用每个watcher
的update
方法
继续回到Watcher
中,可在src/core/observer/watcher.js
文件中查看对update
部分的定义
export default class Watcher {
// ......
/**
* Subscriber interface.
* Will be called when a dependency changes.
*/
update () {
/* istanbul ignore else */
if (this.lazy) {
this.dirty = true
} else if (this.sync) {
this.run()
} else {
queueWatcher(this)
}
}
// ......
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
在watcher
实例的update
方法中,做了两次判断,首先判断this.lazy
是否为true
,当只有watcher
实例为computed watcher
时,该属性才为true
,后面到计算属性小节的时候再做分析,接着判断当前watcher
实例是否为同步watcher
,如果为同步watcher
则直接执行run
方法,到监听器小节时再做分析,接着会调用queueWatcher
方法,将watcher
实例作为参数传递
在src/core/observer/scheduler.js
中可以查看对queueWatcher
方法的定义
const queue: Array<Watcher> = []
const activatedChildren: Array<Component> = []
let has: { [key: number]: ?true } = {}
let circular: { [key: number]: number } = {}
let waiting = false
let flushing = false
let index = 0
// ......
export function queueWatcher (watcher: Watcher) {
const id = watcher.id
if (has[id] == null) {
has[id] = true
if (!flushing) {
queue.push(watcher)
} else {
// if already flushing, splice the watcher based on its id
// if already past its id, it will be run next immediately.
let i = queue.length - 1
while (i > index && queue[i].id > watcher.id) {
i--
}
queue.splice(i + 1, 0, watcher)
}
// queue the flush
if (!waiting) {
waiting = true
if (process.env.NODE_ENV !== 'production' && !config.async) {
flushSchedulerQueue()
return
}
nextTick(flushSchedulerQueue)
}
}
}
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
在queueWatcher
中,首先做了一层has[id] == null
的判断,保证没有重复的watcher
,接着对flushing
做了层判断,为false
的情况将watcher
添加到数组queue
中,else
的部分后面再分析,接着就是判断waiting
,保证只调用了一次nextTick
方法,异步的调用flushSchedulerQueue
方法,对于nextTick
方法的分析会专门写一个小节
flushSchedulerQueue
和queueWatcher
方法定义在同一文件中
function flushSchedulerQueue () {
currentFlushTimestamp = getNow()
flushing = true
let watcher, id
// Sort queue before flush.
// This ensures that:
// 1. Components are updated from parent to child. (because parent is always
// created before the child)
// 2. A component's user watchers are run before its render watcher (because
// user watchers are created before the render watcher)
// 3. If a component is destroyed during a parent component's watcher run,
// its watchers can be skipped.
queue.sort((a, b) => a.id - b.id)
// do not cache length because more watchers might be pushed
// as we run existing watchers
for (index = 0; index < queue.length; index++) {
watcher = queue[index]
if (watcher.before) {
watcher.before()
}
id = watcher.id
has[id] = null
watcher.run()
// in dev build, check and stop circular updates.
if (process.env.NODE_ENV !== 'production' && has[id] != null) {
// ......
}
}
// keep copies of post queues before resetting state
const activatedQueue = activatedChildren.slice()
const updatedQueue = queue.slice()
resetSchedulerState()
// call component updated and activated hooks
callActivatedHooks(activatedQueue)
callUpdatedHooks(updatedQueue)
// devtool hook
/* istanbul ignore if */
if (devtools && config.devtools) {
devtools.emit('flush')
}
}
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
该方法首先对数组queue
中的watcher
根据其id
做了从小到大的排序,源码中主要做了三点解释:
- 组件的更新是从父到子(因为父组件总是先于子组件创建)
- 组件的用户观察程序(监听器
watcher
)在其渲染观察程序(渲染watcher
)之前运行(因为监听器watcher
会优先于渲染watcher
创建) - 如果某个组件在父组件的观察程序运行期间被销毁,它的观察者可以跳过
接着就是循环遍历queue
中的每一项watcher
,需要注意的点是每次循环都会重新计算queue.length
,如果存在watcher.before
方法,则调用,然后调用每一个watcher
实例的run
方法,接着调用resetSchedulerState
方法,重置相关状态,和对activated
和update
钩子函数的调用
接着继续分析watcher.run()
的逻辑,定义在src/core/observer/watcher.js
中。
class Watcher {
// ......
get () {
pushTarget(this)
let value
const vm = this.vm
try {
value = this.getter.call(vm, vm)
} catch (e) {
if (this.user) {
handleError(e, vm, `getter for watcher "${this.expression}"`)
} else {
throw e
}
} finally {
// "touch" every property so they are all tracked as
// dependencies for deep watching
if (this.deep) {
traverse(value)
}
popTarget()
this.cleanupDeps()
}
return value
}
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) {
const info = `callback for watcher "${this.expression}"`
invokeWithErrorHandling(this.cb, this.vm, [value, oldValue], this.vm, info)
} else {
this.cb.call(this.vm, value, oldValue)
}
}
}
}
}
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
run
方法内部先对this.get
方法进行了调用,如果当前watcher
为渲染watcher
,则会重新生成新的vnode
,利用DOM Diff
算法,做局部更新渲染,如果为监听器watcher
,则会根据返回的新值value
和旧值this.value
做比对,如果不相等,或者新值value
为一个不为null
的对象,或者watcher
为一个深度监听器,则将新值value
赋值给this.value
,然后执行监听器回调this.cb
重置状态
function resetSchedulerState () {
index = queue.length = activatedChildren.length = 0
has = {}
if (process.env.NODE_ENV !== 'production') {
circular = {}
}
waiting = flushing = false
}
2
3
4
5
6
7
8
resetSchedulerState
方法中对index、queue、activatedChildren、has、circular、waiting、flushing
都进行了重置
回到queueWatcher
方法中,继续对else
逻辑进行分析
export function queueWatcher (watcher: Watcher) {
const id = watcher.id
if (has[id] == null) {
has[id] = true
if (!flushing) {
queue.push(watcher)
} else {
// if already flushing, splice the watcher based on its id
// if already past its id, it will be run next immediately.
let i = queue.length - 1
while (i > index && queue[i].id > watcher.id) {
i--
}
queue.splice(i + 1, 0, watcher)
}
if (!waiting) {
waiting = true
// ......
nextTick(flushSchedulerQueue)
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
当flushing
为true
时会进入else
分支逻辑,意味着flushSchedulerQueue
在执行中有新的watcher
被添加进来,源码中使用while
循环根据watcher.id
在队列queue
中从后往前进行比对(在flushSchedulerQueue
方法中对queue
中的watcher
进行了排序,所以肯定是有序的watcher
队列),然后找到合适的位置插入到queue
队列中