• 从vue(v2.7.10)源码分析vue是如何收集依赖和触发依赖


    vue对依赖的管理使用的是发布订阅者模式,其中watcher扮演订阅者,Dep扮演发布者。所以dep中会有多个watcher,一个订阅者也可以有多个发布者(依赖)。总共三个过程:定义依赖、收集依赖、触发依赖。下面开始详细讲解三个过程。

    定义依赖

    定义依赖是什么时候开始的呢?通过源码可以发现在执行_init函数的时候会执行initState(vm)方法:

    function initState(vm) {
          ...
          if (opts.data) {
              initData(vm);
          }
          else {
              var ob = observe((vm._data = {}));
              ob && ob.vmCount++;
          }
          ...
      }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    先触发initData方法:

    function initData(vm) {
          var data = vm.$options.data;
          data = vm._data = isFunction(data) ? getData(data, vm) : data || {};
          ...
          var keys = Object.keys(data);
          ...
          var i = keys.length;
          while (i--) {
              var key = keys[i];
              {
                  if (methods && hasOwn(methods, key)) {
                      warn$2("Method \"".concat(key, "\" has already been defined as a data property."), vm);
                  }
              }
              if (props && hasOwn(props, key)) {
                  warn$2("The data property \"".concat(key, "\" is already declared as a prop. ") +
                          "Use prop default value instead.", vm);
              }
              else if (!isReserved(key)) {
                  proxy(vm, "_data", key);
              }
          }
          // observe data
          var ob = observe(data);
          ob && ob.vmCount++;
      }
    
    • 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

    首先会获取data数据,然后执行proxy(vm, “_data”, key):

    var sharedPropertyDefinition = {
          enumerable: true,
          configurable: true,
          get: noop,
          set: noop
      };
    function proxy(target, sourceKey, key) {
          sharedPropertyDefinition.get = function proxyGetter() {
              return this[sourceKey][key];
          };
          sharedPropertyDefinition.set = function proxySetter(val) {
              this[sourceKey][key] = val;
          };
          Object.defineProperty(target, key, sharedPropertyDefinition);
      }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    在vm实例中添加了_data对象并将data的数据给了_data。随后执行observe(data):

    function observe(value, shallow, ssrMockReactivity) {
          ...
          else if (shouldObserve &&
              (ssrMockReactivity || !isServerRendering()) &&
              (isArray(value) || isPlainObject(value)) &&
              Object.isExtensible(value) &&
              !value.__v_skip /* ReactiveFlags.SKIP */) {
              ob = new Observer(value, shallow, ssrMockReactivity);
          }
          return ob;
      }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    主要执行 new Observer(value, shallow, ssrMockReactivity)方法:

    function Observer(value, shallow, mock) {
              if (shallow === void 0) { shallow = false; }
              if (mock === void 0) { mock = false; }
              this.value = value;
              this.shallow = shallow;
              this.mock = mock;
              // this.value = value
              this.dep = mock ? mockDep : new Dep();
              this.vmCount = 0;
              def(value, '__ob__', this);
              if (isArray(value)) {
                 ...
              }
              else {
                  /**
                   * Walk through all properties and convert them into
                   * getter/setters. This method should only be called when
                   * value type is Object.
                   */
                  var keys = Object.keys(value);
                  for (var i = 0; i < keys.length; i++) {
                      var key = keys[i];
                      defineReactive(value, key, NO_INIITIAL_VALUE, undefined, shallow, mock);
                  }
              }
          }
    
    • 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

    主要执行defineReactive:

    function defineReactive(obj, key, val, customSetter, shallow, mock) {
          var dep = new Dep();
          var property = Object.getOwnPropertyDescriptor(obj, key);
          if (property && property.configurable === false) {
              return;
          }
          // cater for pre-defined getter/setters
          var getter = property && property.get;
          var setter = property && property.set;
          if ((!getter || setter) &&
              (val === NO_INIITIAL_VALUE || arguments.length === 2)) {
              val = obj[key];
          }
          var childOb = !shallow && observe(val, false, mock);
          Object.defineProperty(obj, key, {
              enumerable: true,
              configurable: true,
              get: function reactiveGetter() {
                  var value = getter ? getter.call(obj) : val;
                  if (Dep.target) {
                      {
                          dep.depend({
                              target: obj,
                              type: "get" /* TrackOpTypes.GET */,
                              key: key
                          });
                      }
                      if (childOb) {
                          childOb.dep.depend();
                          if (isArray(value)) {
                              dependArray(value);
                          }
                      }
                  }
                  return isRef(value) && !shallow ? value.value : value;
              },
              set: function reactiveSetter(newVal) {
                  var value = getter ? getter.call(obj) : val;
                  if (!hasChanged(value, newVal)) {
                      return;
                  }
                  if (customSetter) {
                      customSetter();
                  }
                  if (setter) {
                      setter.call(obj, newVal);
                  }
                  else if (getter) {
                      // #7981: for accessor properties without setter
                      return;
                  }
                  else if (!shallow && isRef(value) && !isRef(newVal)) {
                      value.value = newVal;
                      return;
                  }
                  else {
                      val = newVal;
                  }
                  childOb = !shallow && observe(newVal, false, mock);
                  {
                      dep.notify({
                          type: "set" /* TriggerOpTypes.SET */,
                          target: obj,
                          key: key,
                          newValue: newVal,
                          oldValue: value
                      });
                  }
              }
          });
          return dep;
      }
    
    • 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
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72

    可以看出新增了一个依赖对象Dep,表示是该数据被哪些组件所依赖,并定义了data下数据的get和set方法。

    收集依赖

    vue是怎么收集依赖的呢?当组件渲染的时候会执行下面的渲染函数:

    var render = function render() {
      var _vm = this,
        _c = _vm._self._c
      return _c("div", [
        _vm._v("\n  " + _vm._s(_vm.num) + "\n  " + _vm._s(_vm.a) + "\n  "),
        _c("button", { on: { click: _vm.addModule } }, [_vm._v("新增")]),
      ])
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    原内容如下:

    
    
    
    
    
    
    • 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
    • 42
    • 43
    • 44
    • 45

    在渲染组件模版的时候会取获取数据,此时会触发data中定义数据的getter方法,此时为当前挂载组件实例化watcher的时候会设置Dep.target。

    Dep.prototype.depend = function (info) {
         if (Dep.target) {
              Dep.target.addDep(this);
              if (info && Dep.target.onTrack) {
                  Dep.target.onTrack(__assign({ effect: Dep.target }, info));
              }
          }
      };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    通知当前依赖的组件去添加依赖,当前依赖的组件会将该依赖添加进入newDeps。相反依赖也可能被多个组件使用,所以在该依赖也有多个组件。

    Watcher.prototype.addDep = function (dep) {
          var id = dep.id;
          if (!this.newDepIds.has(id)) {
              this.newDepIds.add(id);
              this.newDeps.push(dep);
              if (!this.depIds.has(id)) {
                  dep.addSub(this);
              }
          }
      };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    其实本质就是建立组件和变量之间的依赖关系,一个组件可以有多个依赖,一个依赖可以被多个组件使用。

    触发依赖

    当数据发生变化时会触发数据的gettter方法:

    dep.notify({
        type: "set" /* TriggerOpTypes.SET */,
        target: obj,
        key: key,
        newValue: newVal,
        oldValue: value
    });
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    调用当前依赖的notify方法去通知组件更新:

    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();
      }
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    该方法就是获取当前依赖下的组件并调用该组件的update方法:

    Watcher.prototype.update = function () {
       /* istanbul ignore else */
        if (this.lazy) {
            this.dirty = true;
        }
        else if (this.sync) {
            this.run();
        }
        else {
            queueWatcher(this);
        }
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    下面的内容我在另外一篇文章中讲过:vue2.7.10在数据发生变化后是如何更新页面的

    总结:

    1. 定义依赖是在实例化组件的时候执行的此时的Dep.target指向当前vm实例,在initData方法中会遍历data数据并设置get和set方法,每个数据都有一个dep(依赖),表示每个数据都会被多个组件所依赖。
    2. 收集依赖是在执行render方法的时候。该方法触发数据的get方法,建立数据的dep(依赖)和watcher之间的联系。
    3. 触发依赖是在数据发生改变的时候执行。此时会触发数据的set方法,取出当前数据的依赖者(watcher)并循环调用依赖者的update方法更新视图。
  • 相关阅读:
    [SpringMVC笔记] SpringMVC-16-拦截器入门
    2023年【陕西省安全员C证】最新解析及陕西省安全员C证试题及解析
    javaIO流02:IO流原理及流的分类
    如何检验谷歌开发者账号注册资料的可靠性?
    MongoDB集群之分片集群 Shard Cluster
    Linux信号量:POSIX标准接口、实现生产者与消费者模型
    聚观早报 | 东风奕派eπ008将上市;苹果Vision Pro发布会
    交换机与路由器技术:标准ACL、扩展ACL和命名ACL
    山西佳诺德:抖音选品技巧是什么
    Java实现word转PDF
  • 原文地址:https://blog.csdn.net/qq_35094120/article/details/127403397