7.15 watch

到这里,关于响应式系统的分析大部分内容已经分析完毕,我们上一节还遗留着一个问题,Vue对用户手动添加的watch如何进行数据拦截。我们先看看两种基本的使用形式。

  1. // watch选项
  2. var vm = new Vue({
  3. el: '#app',
  4. data() {
  5. return {
  6. num: 12
  7. }
  8. },
  9. watch: {
  10. num() {}
  11. }
  12. })
  13. vm.num = 111
  14. // $watch api方式
  15. vm.$watch('num', function() {}, {
  16. deep: ,
  17. immediate: ,
  18. })

7.15.1 依赖收集

我们以watch选项的方式来分析watch的细节,同样从初始化说起,初始化数据会执行initWatch,initWatch的核心是createWatcher

  1. function initWatch (vm, watch) {
  2. for (var key in watch) {
  3. var handler = watch[key];
  4. // handler可以是数组的形式,执行多个回调
  5. if (Array.isArray(handler)) {
  6. for (var i = 0; i < handler.length; i++) {
  7. createWatcher(vm, key, handler[i]);
  8. }
  9. } else {
  10. createWatcher(vm, key, handler);
  11. }
  12. }
  13. }
  14. function createWatcher (vm,expOrFn,handler,options) {
  15. // 针对watch是对象的形式,此时回调回选项中的handler
  16. if (isPlainObject(handler)) {
  17. options = handler;
  18. handler = handler.handler;
  19. }
  20. if (typeof handler === 'string') {
  21. handler = vm[handler];
  22. }
  23. return vm.$watch(expOrFn, handler, options)
  24. }

无论是选项的形式,还是api的形式,最终都会调用实例的$watch方法,其中expOrFn是监听的字符串,handler是监听的回调函数,options是相关配置。我们重点看看$watch的实现。

  1. Vue.prototype.$watch = function (expOrFn,cb,options) {
  2. var vm = this;
  3. if (isPlainObject(cb)) {
  4. return createWatcher(vm, expOrFn, cb, options)
  5. }
  6. options = options || {};
  7. options.user = true;
  8. var watcher = new Watcher(vm, expOrFn, cb, options);
  9. // 当watch有immediate选项时,立即执行cb方法,即不需要等待属性变化,立刻执行回调。
  10. if (options.immediate) {
  11. try {
  12. cb.call(vm, watcher.value);
  13. } catch (error) {
  14. handleError(error, vm, ("callback for immediate watcher \"" + (watcher.expression) + "\""));
  15. }
  16. }
  17. return function unwatchFn () {
  18. watcher.teardown();
  19. }
  20. };
  21. }

$watch的核心是创建一个user watcher,options.user是当前用户定义watcher的标志。如果有immediate属性,则立即执行回调函数。而实例化watcher时会执行一次getter求值,这时,user watcher会作为依赖被数据所收集。这个过程可以参考data的分析。

  1. var Watcher = function Watcher() {
  2. ···
  3. this.value = this.lazy
  4. ? undefined
  5. : this.get();
  6. }
  7. Watcher.prototype.get = function get() {
  8. ···
  9. try {
  10. // getter回调函数,触发依赖收集
  11. value = this.getter.call(vm, vm);
  12. }
  13. }

7.15.2 派发更新

watch派发更新的过程很好理解,数据发生改变时,setter拦截对依赖进行更新,而此前user watcher已经被当成依赖收集了。这个时候依赖的更新就是回调函数的执行。