在执行初始化数据initState方法中,如果参数中存在watch,会调用initWatch。
// instance/state.js
if (opts.watch && opts.watch !== nativeWatch) {
initWatch(vm, opts.watch)
}
// instance/state.js
function initWatch (vm: Component, watch: Object) {
for (const key in watch) {
const handler = watch[key]
if (Array.isArray(handler)) {
for (let i = 0; i < handler.length; i++) {
createWatcher(vm, key, handler[i])
}
} else {
createWatcher(vm, key, handler)
}
}
}
遍历得到每个watch,然后调用createWatcher方法。
function createWatcher (
vm: Component,
expOrFn: string | Function,
handler: any,
options?: Object
) {
if (isPlainObject(handler)) {
options = handler
handler = handler.handler
}
if (typeof handler === 'string') {
handler = vm[handler]
}
return vm.$watch(expOrFn, handler, options)
}
watch有以下两种三法:
watch: {
a () {
console.log('值是方法')
},
b: {
handler () {
console.log('值是对象')
},
deep: false,
immediate: false
},
c: 'getC'
},
methods: {
getC () {
console.log('值是方法名,方法在method中')
}
}
所以createWatcher就有针对这三种情况做了处理。最终就是获取到watch对应的方法,然后调用vm.$watch(expOrFn, handler, options)。
Vue.prototype.$watch = function (
expOrFn: string | Function,
cb: any,
options?: Object
): Function {
const vm: Component = this
if (isPlainObject(cb)) {
return createWatcher(vm, expOrFn, cb, options)
}
options = options || {}
options.user = true
const watcher = new Watcher(vm, expOrFn, cb, options)
if (options.immediate) {
cb.call(vm, watcher.value)
}
return function unwatchFn () {
watcher.teardown()
}
}
最主要一句就是
const watcher = new Watcher(vm, expOrFn, cb, options)
下面我们通过一个栗子来看:
watch: {
a (cur, old) {
console.log('监控a')
}
}
前面的逻辑比较简单,我们就从new Watcher这一句开始看,expOrFn就是a, cb就是a对应的方法。
export default class Watcher {
vm: Component;
expression: string;
cb: Function;
id: number;
deep: boolean;
user: boolean;
lazy: boolean;
sync: boolean;
dirty: boolean;
active: boolean;
deps: Array<Dep>;
newDeps: Array<Dep>;
depIds: SimpleSet;
newDepIds: SimpleSet;
getter: Function;
value: any;
constructor (
vm: Component,
expOrFn: string | Function,
cb: Function,
options?: ?Object,
isRenderWatcher?: boolean
) {
this.vm = vm
//省略。。。
this.cb = cb
this.expression = process.env.NODE_ENV !== 'production'
? expOrFn.toString()
: ''
// parse expression for getter
if (typeof expOrFn === 'function') {
this.getter = expOrFn
} else {
this.getter = parsePath(expOrFn)
//省略。。。
}
this.value = this.lazy
? undefined
: this.get()
}
// util/lang.js
const bailRE = /[^\w.$]/
export function parsePath (path: string): any {
if (bailRE.test(path)) {
return
}
const segments = path.split('.')
return function (obj) {
for (let i = 0; i < segments.length; i++) {
if (!obj) return
obj = obj[segments[i]]
}
return obj
}
}
这里把expOrFn(也就是a)传入parsePath方法,赋值给当前watcher.getter方法,其实就是获取当然vue实例中,a属性的值(vm.a)。这里只是定义方法,还未调用。紧接着调用watcher.get()方法。
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
}
先把当前watcher赋值给Dep.target,然后调用getter方法,获取当前vue实例中a的值。重点来了!因为vm.a是响应式的,如果获取a的值,必定会调用a的访问器属性get方法,这是a的dep里就收集了当前的观察属性的watcher。一点属性a有变化,就会notify这个watcher,这个watcher就会update,然后排队执行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)
}
}
}
}
run方法就是重新获取一边属性的值,然后调用watcher.cb方法,也就是watch对象中a属性对应的方法,把新旧值分别传入即可。
immediate
如果在观察者属性中设置immediate为true,那么直接调用观察者属性对应的方法。
Vue.prototype.$watch = function (
expOrFn: string | Function,
cb: any,
options?: Object
): Function {
const vm: Component = this
if (isPlainObject(cb)) {
return createWatcher(vm, expOrFn, cb, options)
}
options = options || {}
options.user = true
const watcher = new Watcher(vm, expOrFn, cb, options)
if (options.immediate) {
cb.call(vm, watcher.value)
}
return function unwatchFn () {
watcher.teardown()
}
}
但是这时,参数只能获取到当前的watcher的值,旧值是获取不到的。
deep
在watcher的构造函数中会根据入参设置deep,如果用户传了入参deep为true,就在watcher.get会执行如下方法
if (this.deep) {
traverse(value)
}
export function traverse (val: any) {
_traverse(val, seenObjects)
seenObjects.clear()
}
function _traverse (val: any, seen: SimpleSet) {
let i, keys
const isA = Array.isArray(val)
if ((!isA && !isObject(val)) || Object.isFrozen(val) || val instanceof VNode) {
return
}
if (val.__ob__) {
const depId = val.__ob__.dep.id
if (seen.has(depId)) {
return
}
seen.add(depId)
}
if (isA) {
i = val.length
// val[i] 就是读取值了
while (i--) _traverse(val[i], seen)
} else {
keys = Object.keys(val)
i = keys.length
// val[keys[i]] 就是读取值了
while (i--) _traverse(val[keys[i]], seen)
}
}
这个方法里有取值的动作,因为属性都是响应式的,这个动作会触发属性的get方法,就把当前的watcher收集了。
总结
其实就是让用户自己新建一个watcher,把这个watcher放入要被观察的属性的dep里。这样当a发生变化时,就会notify这个watcher,最终回调到watch对象中a对应的方法。