• 从源码看vue(v2.7.10)中的watch的原理


    由于我在从源码看vue(v2.7.10)的computed的实现原理中详细的讲解过computed的实现,本篇跟computed的原理类似。我就带大家简单分析一下。

    添加依赖

    代码如下:

    
    
    
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41

    初始化watch方法发生在initState(vm)方法中,该方法执行initWatch方法:

    function initState(vm) {
      var opts = vm.$options;
      ...
      if (opts.watch && opts.watch !== nativeWatch) {
        initWatch(vm, opts.watch);
      }
    }
    ...
    function initWatch(vm, watch) {
      for (var key in watch) {
        var handler = watch[key];
        if (isArray(handler)) {
          for (var i = 0; i < handler.length; i++) {
            createWatcher(vm, key, handler[i]);
          }
        }
        else {
          createWatcher(vm, key, handler);
        }
      }
    }
    ...
    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);
     }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32

    initWatch函数会判断当前的watch方法a是不是个数组,不是数组执行else的 createWatcher(vm, key, handler)方法。主要执行vm.$watch(expOrFn, handler, options)方法:

    Vue.prototype.$watch = function (expOrFn, cb, options) {
      var vm = this;
      ...
      options = options || {};
      options.user = true;
      var watcher = new Watcher(vm, expOrFn, cb, options);
      ...
      return function unwatchFn() {
        watcher.teardown();
      };
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    $watch方法主要是实例化了一个观察者Watcher:

    function Watcher(vm, expOrFn, cb, options, isRenderWatcher) {
          ...
          this.dirty = this.lazy; // for lazy watchers
          this.deps = [];
          this.newDeps = [];
          ...
          // expOrFn = 'a'
          if (isFunction(expOrFn)) {
            this.getter = expOrFn;
          }
          else {
            this.getter = parsePath(expOrFn);
            ...
          }
          this.value = this.lazy ? undefined : this.get();
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    由于expOrFn是字符串’a’,所以会执行 parsePath(expOrFn)方法:

    function parsePath(path) {
      ...
      // ['a']
      var segments = path.split('.');
      return function (obj) {
        for (var i = 0; i < segments.length; i++) {
          if (!obj)
            return;
          obj = obj[segments[i]];
        }
        return obj;
      };
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    该方法返回一个函数,并赋值给watcher实例的getter方法。此时执行完this.getter = parsePath(expOrFn)方法,继续执行this.get()方法:

    Watcher.prototype.get = function () {
      pushTarget(this);
      var value;
      var vm = this.vm;
      try {
        value = this.getter.call(vm, vm);
      }
      catch (e) {
       ...
      }
      finally {
        ...
        popTarget();
        this.cleanupDeps();
      }
      return value;
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    该方法执行pushTarget将Dep.target设置为当前观察者(watcher),然后执行 this.getter.call(vm, vm)方法,由于getter方法是parsePath(expOrFn)方法的返回函数:

    // obj = 'vm' segments = ['a']
    function (obj) {
      for (var i = 0; i < segments.length; i++) {
         if (!obj)
           return;
         obj = obj[segments[i]];
       }
       return obj;
     }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    这里可以看出遍历watch方法的key值,这里是’a’,然后去当前的vm实例中获取该变量,触发该变量的getter方法从而建立该观察者和该变量之间的关系。
    在这里插入图片描述
    当前的watch方法a有一个deps放的就是发布者,该发布者的更新要触发订阅者,所以subs里面放的是watch方法a的watcher。

    触发依赖

    触发依赖的过程很简单,当数据改变时会触发变量的setter方法。会获取该变量的订阅者,并执行订阅者中的update方法:

    Dep.prototype.notify = function (info) {
      // stabilize the subscriber list first
      var subs = this.subs.slice();
      ...
      for (var i = 0, l = subs.length; i < l; i++) {
        if (info) {
          var sub = subs[i];
          sub.onTrigger &&
            sub.onTrigger(__assign({ effect: subs[i] }, info));
        }
        subs[i].update();
      }
    };
    
    Watcher.prototype.update = function () {
     // this.lazy = false
      if (this.lazy) {
        this.dirty = true;
      }
      ...
      else {
        queueWatcher(this);
      }
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    最后会执行queueWatcher(this)方法,接下来一系列的过程就是异步执行watcher.run()方法:

    Watcher.prototype.run = function () {
      if (this.active) {
        var value = this.get();
        if (value !== this.value ||
          // Deep watchers and watchers on Object/Arrays should fire even
          // when the value is the same, because the value may
          // have mutated.
          isObject(value) ||
          this.deep) {
          // set new value
          var oldValue = this.value;
          this.value = value;
          if (this.user) {
            var info = "callback for watcher \"".concat(this.expression, "\"");
            // this.cb是watch方法a的函数
            invokeWithErrorHandling(this.cb, this.vm, [value, oldValue], this.vm, info);
          }
          else {
            this.cb.call(this.vm, value, oldValue);
          }
        }
      }
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    该方法获取将新值和旧值放入invokeWithErrorHandling函数中:

    function invokeWithErrorHandling(handler, context, args, vm, info) {
      var res;
      try {
        res = args ? handler.apply(context, args) : handler.call(context);
        if (res && !res._isVue && isPromise(res) && !res._handled) {
          res.catch(function (e) { return handleError(e, vm, info + " (Promise/async)"); });
          res._handled = true;
        }
      }
      catch (e) {
        handleError(e, vm, info);
      }
      return res;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    该方法执行回调,至此watch方法a执行完毕。

    总结

    1. 初始化执行initWatch(vm, opts.watch)方法创建watcher并定义了watcher的getter方法,随后触发getter方法去触发变量的getter方法建立变量和watcher相互之间的联系。
    2. 变量发生变化会触发变量的订阅者的update方法并执行run方法去获取最新的值,并通过执行订阅者的cb方法传入新旧值。
  • 相关阅读:
    JavaSE基础加强、数据结构
    腾讯云阿里云云服务器 Linux 操作系统 BT 宝塔面板快速建站教程
    荷兰国旗问题与快速排序算法
    Kubernetes技术与架构-服务
    Hive字符串函数-空格处理
    [羊城杯 2020]EasySer
    SpringBoot-14-模块开发-环境和首页
    C# Tryparse的使用说明
    [Spring] SpringMVC 简介(二)
    8.缓冲区管理器
  • 原文地址:https://blog.csdn.net/qq_35094120/article/details/127415502