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

目录
  • 定义依赖
  • 收集依赖
  • 触发依赖
  • 总结

定义依赖

定义依赖是什么时候开始的呢?通过源码可以发现在执行_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收集依赖内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • 详解Vue依赖收集引发的问题

    问题背景 在我们的项目中有一个可视化配置的模块,是通过go.js生成canvas来实现的.但是,我们发现这个模块在浏览器中经常会引起该tab页崩溃.开启chrome的任务管理器一看,进入该页面内存和cpu就会暴涨,内存经常会飙到700多M.但是我们的canvas实际的像素只有约500x500,根据一些粗略的计算,大概只占了1M的内存,这个计算过程可参考100*100的 canvas 占多少内存.那么我们这700M内存是哪里来的呢? 定位问题 我们可以使用chrome开发者工具来分析我们的调用栈.

  • 简单实现vue中的依赖收集与响应的方法

    开始 声明一个对象man,可以视为vue中的data let man = { height: 180, weight: 70, wealth: 100000000 } 添加Observer 作用在于将参数对象的属性变为响应式,只要对象的属性被读取或者被修改都能观察到.然后新建一个Observer实例,将man作为参数扔进去.这里的proxyData是将man的属性代理到以man为参数的Observer实例上去. class Observer { constructor(obj) { this.w

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

    目录 定义依赖 收集依赖 触发依赖 总结 定义依赖 定义依赖是什么时候开始的呢?通过源码可以发现在执行_init函数的时候会执行initState(vm)方法: function initState(vm) { ... if (opts.data) { initData(vm); } else { var ob = observe((vm._data = {})); ob && ob.vmCount++; } ... } 先触发initData方法: function initData(v

  • Java源码刨析之ArrayQueue

    目录 ArrayQueue内部实现 ArrayQueue源码剖析 总结 ArrayQueue内部实现 在谈ArrayQueue的内部实现之前我们先来看一个ArrayQueue的使用例子: public void testQueue() { ArrayQueue<Integer> queue = new ArrayQueue<>(10); queue.add(1); queue.add(2); queue.add(3); queue.add(4); System.out.printl

  • Java源码刨析之ArrayDeque

    目录 前言 双端队列整体分析 数组实现ArrayDeque(双端队列)的原理 底层数据遍历顺序和逻辑顺序 ArrayDeque类关键字段分析 ArrayDeque构造函数分析 ArrayDeque关键函数分析 addLast函数分析 addFirst函数分析 doubleCapacity函数分析 pollLast和pollFirst函数分析 总结 前言 在本篇文章当中主要跟大家介绍JDK给我们提供的一种用数组实现的双端队列,在之前的文章LinkedList源码剖析当中我们已经介绍了一种双端队列,

  • Vue编译器AST抽象语法树源码分析

    目录 引言 baseCompile主要核心代码 如何写一个程序来识别 Token parse 函数解析模板字符串 引言 接上篇  Vue编译器源码分析compile 解析 baseCompile主要核心代码 // `createCompilerCreator` allows creating compilers that use alternative // parser/optimizer/codegen, e.g the SSR optimizing compiler. // Here we

  • Vue.use的原理和设计源码探究

    目录 前言 基本使用 源码解析 控制反转 前言 这段时间打算回顾一下Vue的全局方法,脑海里第一个跳出来的方法就是Vue.use,之所以会首先想到它,我觉得和我平时看的面试题相关~~~ Vue.use的原理是面试中常问的点,因为相对于其他全局方法,Vue.use源代码逻辑清晰,如果了解它,也就代表这个人是看过Vue源码的!!! 基本使用 在Vue官网中是这样说明的:通过全局方法 Vue.use(plugin) 使用插件 首先要知道什么是插件,插件通常用来为 Vue 添加全局功能(过滤器.指令.组

  • Vue实现简易翻页效果源码分享

    源码如下: <html> <head> <meta charset="UTF-8"> <title>slidePage</title> <script src="https://cdn.jsdelivr.net/npm/vue@2.5.17/dist/vue.js"></script> <style type="text/css"> *{ margin

  • 关于Redis网络模型的源码详析

    前言 Redis的网络模型是基于I/O多路复用程序来实现的.源码中包含四种多路复用函数库epoll.select.evport.kqueue.在程序编译时会根据系统自动选择这四种库其中之一.下面以epoll为例,来分析Redis的I/O模块的源码. epoll系统调用方法 Redis网络事件处理模块的代码都是围绕epoll那三个系统方法来写的.先把这三个方法弄清楚,后面就不难了. epfd = epoll_create(1024); 创建epoll实例 参数:表示该 epoll 实例最多可监听的

  • pytho matplotlib工具栏源码探析一之禁用工具栏、默认工具栏和工具栏管理器三种模式的差异

    使用matplotlib绘图时,在弹出的窗口中默认是有工具栏的,那么这些工具栏是如何定义的呢? 工具栏的三种模式 matplotlib的基础配置由运行时参数(rcParams)控制,导入matplotlib时,加载matplotlibrc文件生成默认运行时参数. 查看matplotlibrc文件可知#toolbar: toolbar2 # {None, toolbar2, toolmanager},即工具栏有三种模式None.toolbar2和toolmanager,其中默认模式为toolbar

  • python matplotlib工具栏源码探析二之添加、删除内置工具项的案例

    从matplotlib工具栏源码探析一(禁用工具栏.默认工具栏和工具栏管理器三种模式的差异)一文可知matplotlib内置实现了多个工具项的实现,而默认工具栏中的工具项只是其中的一部分,有没有方法直接管理工具栏,添加.删除内置工具项? matplotlib内置的工具项 由源码可知,matplotlib.backend_tools.default_tools变量为字典类型,实例化了基于matplotlib.backend_tools.ToolBase类定义的内置工具项. 源码 default_t

  • python matplotlib工具栏源码探析三之添加、删除自定义工具项的案例详解

    matplotlib工具栏源码探析二(添加.删除内置工具项)探讨了工具栏内置工具项的管理,除了内置工具项,很多场景中需要自定义工具项,官方给出了案例https://matplotlib.org/gallery/user_interfaces/toolmanager_sgskip.html,主要基于matplotlib.backend_managers.ToolManager类实现,即使用工具栏管理器模式. 官方案例解析 下面对官方案例关键点做注释说明. import matplotlib.pyp

随机推荐