为了方便理解,做了流程图,大家可以搭配享用
响应式原理(派发更新)
很久之前的文章说到,依赖收集是通过defineReactive方法中,对对象设置get方法并通过dep.depend()方法,添加到watcher上。
派发更新操作,主要就是在对象的set方法中。
src\core\observer\index.js
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()
}
set方法中,主要是判断值是否改变,如果改变了,则触发dep.notify()
首先回顾一下get(),get方法我们是通过dep.depend()来做收集,看看下面的Dep类,在depend方法中,是通过Dep.target.addDep来给当前watcher添加对应的Dep实例
export default class Dep {
static target: ?Watcher;
id: number;
subs: Array<Watcher>;
constructor () {
this.id = uid++
this.subs = []
}
addSub (sub: Watcher) {
this.subs.push(sub)
}
depend () {
if (Dep.target) {
Dep.target.addDep(this)
}
}
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()
}
}
}
在watcher的addDep()方法中,又再次调用了dep.addSub(this),把自己也同样添加到Dep中
addDep (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)
}
}
}
在Dep.addSub中就是把watcher实例添加到自身的subs数组中,这个属性是一个由watcher实例组成的数组。
在我们派发更新的时候,就会在dep.notify方法中循环遍历subs,并调用每个watcher实例的update方法
update () {
/* istanbul ignore else */
if (this.lazy) {
this.dirty = true
} else if (this.sync) {
this.run()
} else {
queueWatcher(this)
}
}
在update方法中会根据watcher状态走不同流程,正常情况下会执行到
queueWatcher
queueWatcher
src\core\observer\scheduler.js
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)
}
}
}
queueWatcher中涉及到了队列逻辑nextTick,每次更新watcher并不会立即执行,而是会加入到queue中,并且根据watcher.id保证队列中watcher更新只有一次。
flushSchedulerQueue的实现
flushSchedulerQueue会重新排列watcher顺序
这么做主要有以下要确保以下几点:
1.组件的更新由父到子;因为父组件的创建过程是先于子的,所以 watcher 的创建也是先父后子,执行顺序也应该保持先父后子。
2.用户的自定义 watcher 要优先于渲染 watcher 执行;因为用户自定义 watcher 是在渲染 watcher 之前创建的。
3.如果一个组件在父组件的 watcher 执行期间被销毁,那么它对应的 watcher 执行都可以被跳过,所以父组件的 watcher 应该先执行。
遍历queue,拿到每个watcher,如果有before方法则执行,这里再次回顾一下before方法是渲染watcher才有的,作用就是调用beforeUpdate钩子函数。最终还是会执行到watcher.run()方法。
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')
}
}
回过头看watcher.run方法的实现
其实就是调用watcher的get方法获取value,对比之后执行cb回调函数,参数是vm当前实例和新value,旧value,这也是为什么我们在使用watcher时候能拿到新旧值的原因。
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)
}
}
}
}
这里提一下,如果是渲染watcher的话,get方法是执行updateComponent函数。
通过调试可以看到首次渲染生成的渲染watcher第二个参数是updateComponent函数,之前我们也分析过,updateComponent是执行vm.update()的,所以在watcher执行更新的时候就会调用组件的更新引发视图的更新。