watch是为vm的属性(已经在initData方法中被重写get和set方法)的get方法中多收集了一个watcher
具体分析:
function initWatch (vm, watch) { for (var key in watch) { var handler = watch[key]; if (Array.isArray(handler)) { for (var i = 0; i < handler.length; i++) { createWatcher(vm, key, handler[i]); } } else { createWatcher(vm, key, handler); } } }
对每一个watch中的属性,根据方法的key和handler,执行createWatcher 方法。
function createWatcher ( vm, expOrFn, handler, options ) { if (isPlainObject(handler)) { options = handler; handler = handler.handler; } if (typeof handler === 'string') { handler = vm[handler]; } return vm.$watch(expOrFn, handler, options) }
Vue.prototype.$watch = function ( expOrFn, cb, options ) { var vm = this; if (isPlainObject(cb)) { return createWatcher(vm, expOrFn, cb, options) } options = options || {}; options.user = true; var 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(); } };
这里注意 new了一个Watcher,cb是之前的handler,expOrFn是之前的key
这个Watcher在new方法中是这样的
if (typeof expOrFn === 'function') { this.getter = expOrFn; } else { this.getter = parsePath(expOrFn); if (!this.getter) { this.getter = noop; warn( "Failed watching path: "" + expOrFn + "" " + 'Watcher only accepts simple dot-delimited paths. ' + 'For full control, use a function instead.', vm ); } } this.value = this.lazy ? undefined : this.get();
根据expOrFn,也就是key,提取watcher的核心:getter方法,这里expOrFn不是function而是一个key,那么进入parsePath
var bailRE = /[^w.$]/; function parsePath (path) { if (bailRE.test(path)) { return } var segments = path.split('.'); return function (obj) { for (var i = 0; i < segments.length; i++) { if (!obj) { return } obj = obj[segments[i]]; } return obj } }
可以看出,根据key提取到的这个getter方法,其实是key路径最后的那个属性的值。注意,平时我们用watch,一般只监视vm对象的属性(比如叫name),但是其实key可以写成name.prop.prop……,现在简化说,key就是name
然后在new Watcher中,
this.value = this.lazy ? undefined : this.get();
由于laze为false,所以this.get()立即执行,
Watcher.prototype.get = function get () { pushTarget(this); var value; var 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 };
用vm调用getter方法,接着调用vm.name,也就是name的get方法,注意在vm._init方法中,initWatch是在initData之后,initData中defineReactive(vm.data)已经为data的所有可达属性重写了set和get方法,是可以收集watcher的。
get方法闭包中的dep收集这个新new出来的watcher,那么以后vm.name的set方法被调用的时候,就会通过dep.notify调用所收集的watcher的update方法,从而调用run方法,进而调用cb方法。