const vm = new Vue({
data: {
a: 1,
b: 2,
c: 3,
d: 4,
e: {
f: {
g: 5
}
}
},
watch: {
a: function (val, oldVal) {
console.log('new: %s, old: %s', val, oldVal)
},
// 方法名
b: 'someMethod',
// 该回调会在任何被侦听的对象的 property 改变时被调用,不论其被嵌套多深
c: {
handler: function (val, oldVal) {
/* ... */
},
deep: true
},
// 该回调将会在侦听开始之后被立即调用
d: {
handler: 'someMethod',
immediate: true
},
// 你可以传入回调数组,它们会被逐一调用
e: [
'handle1',
function handle2(val, oldVal) {
/* ... */
},
{
handler: function handle3(val, oldVal) {
/* ... */
}
/* ... */
}
],
// watch vm.e.f's value: {g: 5}
'e.f': function (val, oldVal) {
/* ... */
}
}
})
vm.a = 2 // => new: 2, old: 1
初始化watch
src/core/instance/state.ts
export function initState(vm: Component) {
const opts = vm.$options
if (opts.watch && opts.watch !== nativeWatch) {
initWatch(vm, opts.watch)
}
}
function initWatch(vm: Component, watch: Object) {
for (const key in watch) {
const handler = watch[key]
if (isArray(handler)) {
for (let i = 0; i < handler.length; i++) {
createWatcher(vm, key, handler[i])
}
} else {
createWatcher(vm, key, handler)
}
}
}
function createWatcher(
vm: Component,
expOrFn: string | (() => any),
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)
}
1、遍历watch对象,或取每一个key对应的值handler,const handler = watch[key],如果是数组,遍历数组,调用 createWatcher(vm, key, handler[i]),不是数组,就直接调用
2、如果handler是对象,做一下处理,将handler赋值给options,handler对象中的handler才是真正的处理函数,如果handler是个字符串,那就是写在methods中的方法名,直接在实例vm上取
3、 最后返回vm.$watch(expOrFn, handler, options):
src/core/instance/state.ts
Vue.prototype.$watch = function (
expOrFn: string | (() => any),
cb: any,
options?: Record<string, any>
): 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) {
pushTarget()
invokeWithErrorHandling(cb, vm, [watcher.value], vm, info)
popTarget()
}
return function unwatchFn() {
watcher.teardown()
}
}
}
export function invokeWithErrorHandling(
handler: Function,
context: any,
args: null | any[],
vm: any,
info: string
) {
let res
try {
res = args ? handler.apply(context, args) : handler.call(context)
//...
} catch (e: any) {
handleError(e, vm, info)
}
return res
}
1、用户可以调用vm.$watch,传递的cb可以是对象,也可以是函数,如果传递的cb是对象,就调用createWatcher(vm, expOrFn, cb, options)处理一下
2、options.user = true这是一个user watcher,在Watcher类中处理逻辑有些不一样,如果options.immediate为true,执行pushTarget(),没有传参数
Dep.target = undefined,invokeWithErrorHandling,立即执行一遍用户的cb函数,并把watcher.value作为参数传给cb,最后再执行popTarget(),将Dep.target还原为上一次的值
3、最后返回一个unwatch函数,调用watcher.teardown(),取消监听
接下来看一下Watcher中user watcher的逻辑
watch收集依赖
export default class Watcher implements DepTarget {
vm?: Component | null
expression: string
cb: Function
id: number
deep: boolean
user: boolean
lazy: boolean
sync: boolean
dirty: boolean
deps: Array<Dep>
newDeps: Array<Dep>
depIds: SimpleSet
newDepIds: SimpleSet
getter: Function
value: any
post: boolean
constructor(
vm: Component | null,
expOrFn: string | (() => any),
cb: Function,
options?: WatcherOptions | null,
isRenderWatcher?: boolean
) {
if ((this.vm = vm) && isRenderWatcher) {
vm._watcher = this
}
if (options) {
this.deep = !!options.deep
this.user = !!options.user
this.lazy = !!options.lazy
this.sync = !!options.sync
} else {
this.deep = this.user = this.lazy = this.sync = false
}
this.cb = cb
this.id = ++uid // uid for batching
this.active = true
this.post = false
this.dirty = this.lazy // for lazy watchers
this.deps = []
this.newDeps = []
this.depIds = new Set()
this.newDepIds = new Set()
if (isFunction(expOrFn)) {
this.getter = expOrFn
} else {
this.getter = parsePath(expOrFn)
}
this.value = this.lazy ? undefined : this.get()
}
get() {
pushTarget(this)
let value
const vm = this.vm
try {
value = this.getter.call(vm, vm)
} catch (e: any) {
if (this.user) {
handleError(e, vm, `getter for watcher "${this.expression}"`)
} else {
throw e
}
} finally {
if (this.deep) {
traverse(value)
}
popTarget()
this.cleanupDeps()
}
return value
}
}
先判断expOrFn,如果是个函数,直接赋值给getter,this.getter = expOrFn,这里this.lazy = false,所以this.value = this.get(),例如:
// 函数
vm.$watch(
function () {
// 表达式 `this.a + this.b` 每次得出一个不同的结果时
// 处理函数都会被调用。
// 这就像监听一个未被定义的计算属性
return this.a + this.b
},
function (newVal, oldVal) {
// 做点什么
}
)
在get中,调用this.getter,读取a,b,a,b的dep会收集当前的user watcher,
如果expOrFn是a.b.c这种字符串,需要parsePath解析,user watcher会被vm.a.b.c的dep收集
watch触发更新
当watch中依赖的数据更新,也是在setter中通过dep.notify(),然后调用收集的各个watcher的update方法,最后还是放到队列中异步更新,前面文章已经分析过了异步更新
update() {
/* istanbul ignore else */
if (this.lazy) {
this.dirty = true
} else if (this.sync) {
this.run()
} else {
queueWatcher(this)
}
}
最后还是会调用watcher的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中调用用户的cb方法,把新值和旧值传递给cb,如果参数中配置sync:true,就直接调用wather的run方法,不在放到队列中去异步更新
watch的deep
if (this.deep) {
traverse(value)
}
export function traverse(val: any) {
_traverse(val, seenObjects)
seenObjects.clear()
return val
}
function _traverse(val: any, seen: SimpleSet) {
let i, keys
const isA = 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
while (i--) _traverse(val[i], seen)
} else if (isRef(val)) {
_traverse(val.value, seen)
} else {
keys = Object.keys(val)
i = keys.length
while (i--) _traverse(val[keys[i]], seen)
}
}