Vue中Watch的源码相比于Compute的源码实现简单了很多,以下是我的学习笔记:
1、初始Vue时会通过initState方法,在代码中进行opts.watch字段的判断,从而进行initState方法对Watch进行初始化。(和Compute的一样)。
// 部分实现
function Vue(){
... 其他处理
initState(this)
...解析模板,生成DOM 插入页面
}
function initState(vm) {
...处理 data,props,computed 等数据
if (opts.watch) {
initWatch(this, vm.$options.watch);
}
}
2、initWatch分析(为每个watch创建专门的watcher也就是createWatcher )
// 部分实现
function initWatch(vm, watch) {
for (var key in watch) {
var watchOpt = watch[key];
createWatcher(vm, key, handler);
}
}
function createWatcher(
// expOrFn 是 key,handler 可能是对象
vm, expOrFn, handler,opts
) {
// 监听属性的值是一个对象,包含handler,deep,immediate
// 下面的判断就是获取用户定义的watch函数
if (typeof handler ==="object") {
opts= handler
handler = handler.handler
}
// 回调函数是一个字符串,从 vm 获取
if (typeof handler === 'string') {
handler = vm[handler]
}
// expOrFn 是 key,options 是watch 的全部选项
vm.$watch(expOrFn, handler, opts)
}
3、 vm.$watch(),这里才是watch-watcher的细节实现
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)
}
// 为watch的watcher添加user标志
options = options || {}
options.user = true
const watcher = new Watcher(vm, expOrFn, cb, options)
// immediate为true时直接回调
if (options.immediate) {
try {
cb.call(vm, watcher.value)
} catch (error) {
handleError(error, vm, `callback for immediate watcher "${watcher.expression}"`)
}
}
......
}
我们可以看到当 immediate 设置为true时,在读取了 监听的数据的值 之后,便立即调用一遍你设置的监听回调,然后传入刚读取的值。
观察 Watcher 源码:
export default class Watcher {
constructor (
vm: Component,
expOrFn: string | Function,
cb: Function,
options?: ?Object,
isRenderWatcher?: boolean
) {
this.vm = vm
// options
if (options) {
this.deep = !!options.deep
this.user = !!options.user
this.lazy = !!options.lazy
this.sync = !!options.sync
this.before = options.before
} else {
this.deep = this.user = this.lazy = this.sync = false
}
this.cb = cb
this.id = ++uid // uid for batching
this.active = true
this.dirty = this.lazy // for lazy watchers
this.deps = []
this.newDeps = []
this.depIds = new Set()
this.newDepIds = new Set()
// 对于watch-watcher来说这一步就相当于拿hanlder
if (typeof expOrFn === 'function') {
this.getter = expOrFn
} else {
this.getter = parsePath(expOrFn)
}
// 当 watch-watcher 到这一步时,直接调用了 this.get, 相当于直接调用了watch的handler
this.value = this.lazy
? undefined
: this.get()
}
}
简单来说当watch-watcher进入时就会触发 this.get(),也就是用户自己定义的 handler
get () {
pushTarget(this)
let value
const vm = this.vm
try {
// 这一步相当于调用watch的handler
value = this.getter.call(vm, vm)
} catch (e) {
if (this.user) {
handleError(e, vm, `getter for watcher "${this.expression}"`)
} else {
throw e
}
} finally {
// 若定义了deep,这会遍历watch监听的那个对象的所有key值
if (this.deep) {
// 递归每一个对象或者数组,触发它们的getter,
// 使得对象或数组的每一个成员都被依赖收集,形成一个“深(deep)”依赖关系
traverse(value)
}
popTarget()
this.cleanupDeps()
}
return value
}
因为this.get(),需要获取到监听的值,这样就相当于触发了监听值的getter,从而触发了 监听值的 dep.depend()
又因为这时候的 Dep.target 指向的是 watch-watcher,这样就相当于 监听值 收集到 watch-watcher 进了它的 Dep里
所以当 监听值变化时,就会触发 watch, 这样监听就成功了
由上面的分析我们知道监听的数据变化的时候,就能通知 watch-watcher 更新,所谓通知更新,就是手动调用 watch.update
以下为 watcher.update 部分源码:
Watcher.prototype.update= function() {
var value = this.get();
if (this.deep) {
var oldValue = this.value;
this.value = value;
// cb 是监听回调
this.cb.call(this.vm, value, oldValue);
}
};
很简单嘛,就是读取一遍值,然后保存新值,接着 调用 监听回调也就是用户定义的handler,并传入新值和 旧值