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= handlerhandler = 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 = thisif (isPlainObject(cb)) {return createWatcher(vm, expOrFn, cb, options)}// 为watch的watcher添加user标志options = options || {}options.user = trueconst 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// optionsif (options) {this.deep = !!options.deepthis.user = !!options.userthis.lazy = !!options.lazythis.sync = !!options.syncthis.before = options.before} else {this.deep = this.user = this.lazy = this.sync = false}this.cb = cbthis.id = ++uid // uid for batchingthis.active = truethis.dirty = this.lazy // for lazy watchersthis.deps = []this.newDeps = []this.depIds = new Set()this.newDepIds = new Set()// 对于watch-watcher来说这一步就相当于拿hanlderif (typeof expOrFn === 'function') {this.getter = expOrFn} else {this.getter = parsePath(expOrFn)}// 当 watch-watcher 到这一步时,直接调用了 this.get, 相当于直接调用了watch的handlerthis.value = this.lazy? undefined: this.get()}
}
1、依赖收集
简单来说当watch-watcher进入时就会触发 this.get(),也就是用户自己定义的 handler
get () {pushTarget(this)let valueconst vm = this.vmtry {// 这一步相当于调用watch的handlervalue = 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, 这样监听就成功了
2、触发更新
由上面的分析我们知道监听的数据变化的时候,就能通知 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,并传入新值和 旧值