上一小节分析了响应式数据依赖收集的过程,这一节接着分析当数据发生改变通知更新的过程,首先看下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队列中