# Notify
这一节介绍响应式系统是如何派发通知的。
回顾defineReactive方法中的set逻辑
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: function reactiveGetter () {
...
},
set: function reactiveSetter (newVal) {
const value = getter ? getter.call(obj) : val
if (newVal === value || (newVal !== newVal && value !== value)) {
return
}
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()
}
})
判断新旧值是否发生变化,如果没变化不做处理。如果在非生产环境用户自定义了customSetter方法,执行自定义setter方法,最后通过observe(newVal)使新设置的值变成响应式数据,最关键一步是调用dep.notify()方法。
打开mysrc/core/observer/dep.js,找到notify方法
notify () {
// stabilize the subscriber list first
const subs = this.subs.slice()
if (process.env.NODE_ENV !== 'production' && !config.async) {
subs.sort((a, b) => a.id - b.id)
}
for (let i = 0, l = subs.length; i < l; i++) {
subs[i].update()
}
}
其实就是把数据收集的Watcher实例遍历执行update()方法。
在mysrc/core/observer/watcher.js中查找update方法:
update () {
/* istanbul ignore else */
if (this.lazy) {
this.dirty = true
} else if (this.sync) {
this.run()
} else {
queueWatcher(this)
}
}
lazy的作用与计算属性computed有关,这个后面再详细介绍,此时lazy为false,sync代表同步更新这里也不展开介绍,sync的值也为false,所以最终执行的是queueWatcher。
打开mysrc/core/observer/scheduler.js查看queueWatcher方法
const queue = []
export function queueWatcher (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)
}
}
}
该文件维护了一个队列,所有需要执行的Watcher都放入这个队列,在入队之前先对去重做处理,把要执行的操作存在队列中,是为了防止每修改一个数据就触发一次渲染,如果一次修改多个data,并不会每次修改都会触发更新,而是存在队列中在下一个tick中一块执行。最后通过nextTick执行flushSchedulerQueue。nextTick的目的是为了保证这些更新操作都在下一个任务tick执行,nextTick的实现逻辑暂且不管,先看一下flushSchedulerQueue方法
function flushSchedulerQueue () {
currentFlushTimestamp = getNow()
flushing = true
let watcher, id
queue.sort((a, b) => a.id - b.id)
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方法,打开mysrc/core/observer/watcher.js查找run方法
run () {
if (this.active) {
const value = this.get()
if (
value !== this.value ||
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)
}
}
}
}
run方法首先调用this.get方法求值,this.get内部会执行this.getter方法,对于renderWatcher前面分析this.getter就是传给Watcher的updateComponent方法,所以在updateComponent内部会执行_render方法,从而实现重新渲染的效果。
this.user为true时表示这个watcher是用户自己写的watcher,这里渲染watcher是vue内置的watcher。最终都是执行this.cb方法。其实invokeWithErrorHandling的作用就是把this.cb放在try...catch结构体执行,为了方便捕获错误。事实上我们通常在vue中这样使用watch
vm.$watch('a.b.c', function (newVal, oldVal) {
// 做点什么
})
// 或者
watch: {
// 如果 `question` 发生改变,这个函数就会运行
question: function (newQuestion, oldQuestion) {
this.answer = 'Waiting for you to stop typing...'
this.debouncedGetAnswer()
}
}
调用this.cb回调方法时把新旧值都传给了cb,所以用户自己写的watch能在回调方法里面获取新值和旧值。对于renderWatcher,this.cb是个空函数noop。