Vue收集依赖与触发依赖源码刨析

 更新时间:2022年10月21日 14:44:16   作者:Young soul2  
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++;
      }
      ...
  }

先触发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++;
  }

首先会获取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);
  }

在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;
  }

主要执行 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);
              }
          }
      }

主要执行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;
  }

可以看出新增了一个依赖对象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("新增")]),
  ])
}

原内容如下:

<template>
  <div>
    {{num}}
    {{a}}
    <button @click="addModule">新增</button>
  </div>
</template>
<script>
export default {
  name: "TestWebpackTest",
  mounted() {
    console.log(this);
  },
  data() {
    return {
      num: 1,
      a:2
    };
  },
  computed:{
    getNum(){
      return this.num+Math.random()
    },
    getA(){
      return this.a+Math.random()
    }
  },  
  methods: {
    addModule() {
      this.num++;
    }
  }
};
</script>
<style lang="scss">
div {
  .test {
    width: 10px;
    height: 15px;
    background-color: blue;
  }
}
</style>

在渲染组件模版的时候会取获取数据,此时会触发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));
          }
      }
  };

通知当前依赖的组件去添加依赖,当前依赖的组件会将该依赖添加进入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);
          }
      }
  };

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

触发依赖

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

dep.notify({
    type: "set" /* TriggerOpTypes.SET */,
    target: obj,
    key: key,
    newValue: newVal,
    oldValue: value
});

调用当前依赖的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();
  }
};

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

Watcher.prototype.update = function () {
   /* istanbul ignore else */
    if (this.lazy) {
        this.dirty = true;
    }
    else if (this.sync) {
        this.run();
    }
    else {
        queueWatcher(this);
    }
};

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

总结

  • 定义依赖是在实例化组件的时候执行的此时的Dep.target指向当前vm实例,在initData方法中会遍历data数据并设置get和set方法,每个数据都有一个dep(依赖),表示每个数据都会被多个组件所依赖。
  • 收集依赖是在执行render方法的时候。该方法触发数据的get方法,建立数据的dep(依赖)和watcher之间的联系。
  • 触发依赖是在数据发生改变的时候执行。此时会触发数据的set方法,取出当前数据的依赖者(watcher)并循环调用依赖者的update方法更新视图。

到此这篇关于Vue收集依赖与触发依赖源码刨析的文章就介绍到这了,更多相关Vue收集依赖内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • vue动态子组件的两种实现方式

    vue动态子组件的两种实现方式

    这篇文章主要介绍了vue动态子组件的两种实现方式,非常不错,具有一定的参考借鉴价值,需要的朋友可以参考下
    2019-09-09
  • vue实现html转化pdf并复制文字

    vue实现html转化pdf并复制文字

    这篇文章主要为大家详细介绍了vue实现html转化pdf的两种方式,分别为能复制文字和不能复制文字的方法,有需要的小伙伴可以跟随小编一起学习一下
    2024-10-10
  • Vue动态数据实现 el-select 多级联动、数据回显方式

    Vue动态数据实现 el-select 多级联动、数据回显方式

    这篇文章主要介绍了Vue动态数据实现 el-select 多级联动、数据回显方式,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-07-07
  • vue中require与import的区别详解

    vue中require与import的区别详解

    这篇文章主要介绍了vue中require与import的区别详解,require相当于module.exports的传送门,module.exports后面的内容是什么,require的结果就是什么,对象、数字、字符串、函数,再把require的结果赋值给某个变量,需要的朋友可以参考下
    2023-10-10
  • 使用Vant完成Dialog弹框案例

    使用Vant完成Dialog弹框案例

    这篇文章主要介绍了使用Vant完成Dialog弹框案例,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2020-11-11
  • vue和react中props变化后如何修改state

    vue和react中props变化后如何修改state

    这篇文章主要介绍了vue和react中props变化后如何修改state,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-08-08
  • Vue+Element-UI中el-table动态合并单元格:span-method方法代码详解

    Vue+Element-UI中el-table动态合并单元格:span-method方法代码详解

    el-table是element-ui提供的表格组件,可以用于展示和操作数据,这篇文章主要给大家介绍了关于Vue+Element-UI中el-table动态合并单元格:span-method方法的相关资料,需要的朋友可以参考下
    2023-09-09
  • Vue自定义指令详细

    Vue自定义指令详细

    这篇文章主要介绍了Vue自定义指令,文章从背景开始详细介绍Vue自定义指令的详细内容,随着Vue自定义指令的相关资料展开具体内容,需要的朋友可以参考一下
    2021-11-11
  • 详解vue-cli 脚手架项目-package.json

    详解vue-cli 脚手架项目-package.json

    本篇文章主要介绍了详解vue-cli 脚手架项目-package.json,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2017-07-07
  • vue element-ui Radio单选框默认值选不中的原因:混用字符和数字问题

    vue element-ui Radio单选框默认值选不中的原因:混用字符和数字问题

    这篇文章主要介绍了vue element-ui Radio单选框默认值选不中的原因:混用字符和数字问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2023-12-12

最新评论