watch和computed是姊妹篇,前言同上。
为啥姊妹呢,因为computed初始化完了就是初始化watch:
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); } } }
这个方法没啥好解说的,直接到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) }
从上面的代码可以看出初始化的时候定义在watch上的那些key-value和调用实例的$watch走的是一回事。所以还是看看$watch:
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***mediate) { try { cb.call(vm, watcher.value); } catch (error) { handleError(error, vm, ("callback for immediate watcher \"" + (watcher.expression) + "\"")); } } return function unwatchFn () { watcher.teardown(); } }; }
看到没,又是watcher,这里的options是undefined,但是要注意到,options.user = true; 这个很重要是因为刷新任务队列的时候watch里的watcher的cb回调函数就是根据这个参数去调用的。我在写到这里的时候其实想吐槽一句vue就是vue框架的的整个机制大到vuex,vue-router小到这种数据的相应式绑定都跟watcher有关,不过我第一次发现这个的时候倒是觉着挺妙的,其他的倒也没啥。
(n年后的补充:一个工具在一个框架内部要去实现响应式更新,无论路由也好,状态管理也好,其,要实现更新,更定是要利用框架现有的机制的,vue的watcher,react的setstate等等,不然能咋办呢)
这个函数里需要注意到一个地方就是实例化watcher的时候,是吧当前watch的key传进去了,此时,expOrFn是一个字符串类型,这个时候,Watcher构造函数的执行是这样的:
this.getter = parsePath(expOrFn);
parsePath长这样:
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 } }
返回的闭包函数就是当前watcher的getter,闭包了当前的'key',这个key也可以是‘key.key.key’,这个有啥用放到后边说,watcher走到最后一步的时候会调用watcher实例的get进行依赖收集,然后依赖收集的时候有这么一句:
value = this.getter.call(vm, vm);
所以再回到上边那个闭包函数中,此时调用的时候obj参数就引用了当前的vm,而下边的for循环则是一步步具体化watch要watch的路径,触发这条路径上所有的get,进行依赖收集,比方说我们watch了一个'key.key',那么vm['key'],vm['key']['key']会依次调用,此时watch里key值派生的watcher也被依赖收集机制 收集了进去。然后一个watch就这么初始化完毕啦。