准备
vue版本号2.6.12,为方便分析,选择了runtime+compiler版本。
回顾
如果有感兴趣的同学可以看看我之前的源码分析文章,这里呈上链接:《Vue源码分析系列:目录》
派发更新
前面一篇我们了解到Vue在data
的get
过程中收集了对应数据的Watcher
实例,由这些watcher
牵引着相关的依赖。这次我们要讲的是data
的set
过程中的逻辑,在get
中收集了watcher
之后,在set
过程中就会通知这些watcher
,由这些watcher
去更新依赖,这就是今天要讲的派发更新的过程。
defineReactive
进入defineReactive
查看set
过程的操作:
//设置该值时,派发更新
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();
},
前面的一大串都可以忽略不看,直接看最后一行dep.notify()
。
进入dep.notify()
。
dep.notify()
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()
}
一开始对实例上的subs
列表进行了一次浅拷贝。然后遍历这个Watcher
列表,调用Watcher
实例上的update
方法。
watcher.update()
update() {
/* istanbul ignore else */
if (this.lazy) {
this.dirty = true;
} else if (this.sync) {
this.run();
} else {
queueWatcher(this);
}
}
判断是不是一个懒watcher
(也就是之后篇章会介绍到的computed watcher
),显然这里不是,再判断是不是一个同步wather
,显然我们没有定义过什么同步属性,所以直接进入else
,也就是调用queueWatcher
方法。
queueWatcher
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)
}
}
}
先判断是不是正在flushing
(默认为false
),flushing
这个变量我们接下来就能见到,这里为false
,所以执行queue.push(watcher)
;再往下,判断是不是正在waiting
(默认为false
),这里为false
,进入判断,然后更改waiting
为true
,之后直接nextTick(flushSchedulerQueue)
,这个nextTick
我们接下来几篇会讲到,我们这边直接就当做调用了flushSchedulerQueue
,进入flushSchedulerQueue
。
flushSchedulerQueue
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) {
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
}
}
}
// 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')
}
}
一上来先是更改了flushing
为true
。之后对queue
待更新队列进行了一个排序。在Vue的源码注释中可以看到为什么要排序:
- 数据更新是从父传到子的
- 用户自定义的
watcher
需要在渲染watcher
之前执行 - 如果在父组件
watcher
执行是销毁实例,就不需要执行子watcher
了
然后就是遍历这个watcher
队列,如果实例上存在before
方法,就执行before
方法,在mountComponent
中可以看到一个渲染watcher
实例化时回传入一个before
方法:
new Watcher(
vm,
updateComponent,
noop,
{
before() {
if (vm._isMounted && !vm._isDestroyed) {
callHook(vm, "beforeUpdate");
}
},
},
true /* isRenderWatcher */
);
所以这个时候会触发beforeUpdate
钩子函数,接下来调用了watcher
实例上的run
方法,这个我们一会看,继续往下,之后是一个无限循环判断,之后调用resetSchedulerState
,在这里面将wating
和flushing
至为false
。 最后是keep-alive
的两个钩子。
接下来我们进入watcher.run()
watcher.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);
}
}
}
}
先调用了get
- 如果这里的
watcher
是个渲染Watcher
,也就是在mountComponent
中实例化的Watcher
,调用这个get
方法后就会去执行updateComponent
方法,也就是render
、patch
的过程。 - 如果是用户定义的普通
Watcher
,调用这个get
就会获取数据的最新值,然后比较新旧值,如果是数据类型是对象的话还有deep
选项来进行深度比较。如果新旧值不一致就会调用用户定义的回调函数并传入新旧值。
到这,派发更新的过程就解析完毕了。
总结
经过对以上源码的阅读我们了解到,现在我们认识的Watcher
有两种类型,一种是渲染Watcher
,一种是用户自定义的Watcher
,如果是渲染Watcher
,在派发更新的过程中会执行patch
方法,也就是更新DOM;如果是用户定义的Watcher
,就会调用用户定义的回调函数,并传入数据的新旧值。