分析源码不求行行都懂,大致分析下watch得执行原理
- 在vue源码中找到initWatch
基本得逻辑就是initWatch —> createWatcher() ----> vm.$watch() ----> new Watcher();
**1.initWatch **
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()
2.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)
}
createWatcher前面部分代码就是针对watch得不同写法做了处理,比如字符串写法,函数写法,对象写法。最后调用了$watch得方法
3.$watch
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) {
try {
cb.call(vm, watcher.value)
} catch (error) {
handleError(error, vm, `callback for immediate watcher "${watcher.expression}"`)
}
}
return function unwatchFn () {
watcher.teardown()
}
}
在 w a t c h 这 个 方 法 中 注 意 , 它 得 核 心 是 n e w W a t c h e r ( v m , e x p O r F n , c b , o p t i o n s ) , 我 们 可 以 往 上 推 看 到 参 数 一 次 是 t h i s , 字 符 串 , 执 行 逻 辑 , u n d e f i n e d , 注 意 : 所 有 实 现 w a t c h 得 原 理 就 在 这 个 W a t c h e r 中 实 现 。 返 回 w a t c h e r . t e a r d o w n ( ) , 是 去 除 监 听 , 是 为 了 在 我 们 代 码 中 直 接 写 t h i s . watch这个方法中注意,它得核心是 new Watcher(vm, expOrFn, cb, options),我们可以往上推看到参数一次是 this,字符串,执行逻辑,undefined, 注意:所有实现watch得原理就在这个Watcher中实现。 返回 watcher.teardown(),是去除监听,是为了在我们代码中直接写this. watch这个方法中注意,它得核心是newWatcher(vm,expOrFn,cb,options),我们可以往上推看到参数一次是this,字符串,执行逻辑,undefined,注意:所有实现watch得原理就在这个Watcher中实现。返回watcher.teardown(),是去除监听,是为了在我们代码中直接写this.watch()得时候不用去执行监听逻辑。
4.new Watcher
1.在constructor中有这一段逻辑,会执行parsePath方法
if (typeof expOrFn === 'function') {
this.getter = expOrFn
} else {
this.getter = parsePath(expOrFn)
if (!this.getter) {
this.getter = noop
process.env.NODE_ENV !== 'production' && warn(
`Failed watching path: "${expOrFn}" ` +
'Watcher only accepts simple dot-delimited paths. ' +
'For full control, use a function instead.',
vm
)
}
}
2.parsePath方法返回得是一个函数(这种用法为柯里化函数),返回得函数中都是要去访问对应data得 get set,方法得,这时候就会做一个依赖收集,和派发更新。
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
}
}
3.接着往下看,是执行get(),pushTarget(this)会将Dep.target设置为当前得watch-watcher,然后执行getter,上面说了getter返回得是一个函数,里面会做依赖收集和派发更新,这个时候watch-watcher 就会收集进入dep,最后促发派发更新 触发watch-watcher得update方法
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
}
4。调用queueWatcher(),这里源码中可以去看下这个对应的方法中会执行flushSchedulerQueue(),最后执行watcher得run 方法
update () {
/* istanbul ignore else */
if (this.lazy) {
this.dirty = true
} else if (this.sync) {
this.run()
} else {
queueWatcher(this)
}
}
*5.run(),最后又返回到了this.cb.call,watch得执行回调中去了,上面我们说了在new watcher得时候cb 就是,watch得执行逻辑,
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)
}
}
}
}
总结:
- vue在初始化watch得时候都会将watch当作一个对象循环处理,并且兼容它得几种写法
- 它实现得核心就是 w a t c h 这 个 方 法 , 它 会 实 例 化 一 个 w a t c h − w a t c h e r , watch这个方法,它会实例化一个watch-watcher, watch这个方法,它会实例化一个watch−watcher,watch返回是一个函数清楚监听,是为了在代码中直接写this.$watch 不会触发这些执行逻辑。
- watch-watcher中得处理,parsePath会先处理字符串,getter返回得是一个函数。
- 执行watcher.get方法,替换全局watcher,执行getter完成依赖收集。
- 当监听得数据变化时,也会触发它得watch-watcher,来调用update方法,其实最后执行得是它得run方法,里面会执行watcher得对应数据得执行逻辑