Vue watch原理源码层深入讲解

目录
  • 添加依赖
  • 触发依赖
  • 总结

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

添加依赖

代码如下:

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

初始化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);
 }

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();
  };
};

$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();
    }

由于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;
  };
}

该方法返回一个函数,并赋值给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;
};

该方法执行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;
 }

这里可以看出遍历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);
  }
};

最后会执行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);
      }
    }
  }
};

该方法获取将新值和旧值放入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;
}

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

总结

  • 初始化执行initWatch(vm, opts.watch)方法创建watcher并定义了watcher的getter方法,随后触发getter方法去触发变量的getter方法建立变量和watcher相互之间的联系。
  • 变量发生变化会触发变量的订阅者的update方法并执行run方法去获取最新的值,并通过执行订阅者的cb方法传入新旧值。

到此这篇关于Vue watch原理源码层深入讲解的文章就介绍到这了,更多相关Vue watch原理内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • 老生常谈Vue中的侦听器watch

    目录 一.侦听器watch 1.1.初识侦听器watch 1.2.Vue的data的watch 1.3.Vue的watch侦听选项 一.侦听器watch (思维导图不太完善,因为是按照自己看懂的方式记的,如有错误,还请指正) 1.1.初识侦听器watch watch:观看,监视 那么什么是侦听器watch呢 开发中我们在data返回的对象中定义了数据,这个数据通过插值语法等方式绑定到template中: 当数据变化时,template会自动进行更新来显示最新的数据: 但是在某些情况下,我们希望在

  • Vue3源码分析侦听器watch的实现原理

    目录 watch 的本质 watch 的函数签名 侦听多个源 侦听单一源 watch 的实现 watch 函数 source 参数 cb 参数 options 参数 doWatch 函数 doWatch 函数签名 初始化变量 递归读取响应式数据 定义清除副作用函数 封装 scheduler 调度函数 设置 job 的 allowRecurse 属性 flush 选项指定回调函数的执行时机 创建副作用函数 执行副作用函数 返回匿名函数,停止侦听 总结 watch 的本质 所谓的watch,其本质就

  • vue3中watch和watchEffect实战梳理

    目录 前言 watch watch监听单个数据 watch监听多个数据 watch监听对象 watch监听对象的某一个值 watchEffect watchEffect副作用 停止监听 区别 前言 watch和watchEffect都是vue3中的监听器,但是在写法和使用上是有区别的,这篇文章主要是介绍一下watch和watchEffect的使用方法以及他们之间的区别. watch watch监听单个数据 <template> <input type="text" v

  • Vue 中的 computed 和 watch 的区别详解

    目录 computed 注意 应用场景 watch 总结 computed computed 看上去是方法,但是实际上是计算属性,它会根据你所依赖的数据动态显示新的计算结果.计算结果会被缓存,computed 的值在 getter 执行后是会缓存的,只有在它依赖的属性值改变之后,下一次获取 computed 的值时才会重新调用对应的 getter 来计算. 1)下面是一个比较经典简单的案例 <template> <div class="hello"> {{ful

  • 详解Vue3中侦听器watch的使用教程

    目录 watch 侦听器使用. 侦听器监听 reactive 监听多个参数执行各自逻辑 监听多个参数执行相同逻辑 上一节我们简单的介绍了一下vue3 项目中的计算属性,这一节我们继续 vue3 的基础知识讲解. 这一节我们来说 vue3 的侦听器. 学过 vue2 的小伙伴们肯定学习过侦听器,主要是用来监听页面数据或者是路由的变化,来执行相应的操作,在 vue3里面呢,也有侦听器的用法,功能基本一样,换汤不换药的东西. 侦听器是常用的 Vue API 之一,它用于监听一个数据并在数据变动时做一些

  • 使用Vue逐步实现Watch属性详解

    目录 watch 初始化watch deep.immdediate属性 结语 watch 对于watch的用法,在Vue文档 中有详细描述,它可以让我们观察data中属性的变化.并提供了一个回调函数,可以让用户在属性值变化后做一些事情. watch对象中的value分别支持函数.数组.字符串.对象,较为常用的是函数的方式,当想要观察一个对象以及对象中的每一个属性的变化时,便会用到对象的方式. 下面是官方的一个例子,相信在看完之后就能对watch的几种用法有大概的了解: var vm = new

  • Vue3中watch监听使用详解

    目录 Vue2使用watch Vue3使用watch 情况1 情况2 情况3 情况4 情况5 特殊情况 总结 Vue2使用watch <template> <div>总合:{{ sum }}<button @click="sum++">点击累加</button></div> </template> <script> import { ref } from "vue"; export

  • Vue watch原理源码层深入讲解

    目录 添加依赖 触发依赖 总结 由于我在从源码看vue(v2.7.10)的computed的实现原理中详细的讲解过computed的实现,本篇跟computed的原理类似.我就带大家简单分析一下. 添加依赖 代码如下: <template> <div> {{a}} <button @click="addModule">新增</button> </div> </template> <script> exp

  • Vue解读之响应式原理源码剖析

    目录 初始化 initState() initProps() initData() observe() Observer defineReactive() 依赖收集 Dep Watcher 依赖收集过程 移除订阅 派发更新 notify() update() queueWatcher() flushSchedulerQueue() updated() defineProperty 缺陷及处理 Vue.set() 重写数组方法 总结 先看张图,了解一下大体流程和要做的事 初始化 在 new Vue

  • spring-session简介及实现原理源码分析

    一:spring-session介绍 1.简介 session一直都是我们做集群时需要解决的一个难题,过去我们可以从serlvet容器上解决,比如开源servlet容器-tomcat提供的tomcat-redis-session-manager.memcached-session-manager. 或者通过nginx之类的负载均衡做ip_hash,路由到特定的服务器上.. 但是这两种办法都存在弊端. spring-session是spring旗下的一个项目,把servlet容器实现的httpSe

  • python装饰器原理源码示例分析

    目录 前言 一.什么是装饰器 二.为什么要用装饰器 三.简单的装饰器 四.装饰器的语法糖 五.装饰器传参 六.带参数的装饰器 七.类装饰器 八.带参数的类装饰器 九.装饰器的顺序 前言 最近有人问我装饰器是什么,我就跟他说,其实就是装饰器就是类似于女孩子的发卡.你喜欢的一个女孩子,她可以有很多个发卡,而当她戴上不同的发卡,她的头顶上就是装饰了不同的发卡.但是你喜欢的女孩子还是你喜欢的女孩子.如果还觉得不理解的话,装饰器就是咱们的手机壳,你尽管套上了手机壳,但并不影响你的手机功能,可你的手机还是该

  • Spring的Model 和 Map的原理源码解析

    Model 和 Map 为什么在Model和Map中放值传入后会出现在request的上面. 9.1.源码解析 准备测试代码 @GetMapping("/goto") public String go(HttpServletRequest request, Map<String,Object> map, Model model){ request.setAttribute("msg","传过来...."); map.put("

  • MyBatis框架底层的执行原理源码解析

    目录 1.前言 2.案例项目源码 3.MyBatis源码解析底层执行原理 3.1 读取mybatis配置文件创建出SqlSeesionFactory对象 3.2 通过SqlSeesionFactory对象进而创建出SqlSession对象 3.3 通过SqlSession的getMapper获取到接口代理对象 3.4 通过mapper接口的代理对象执行CRUD 1.前言 MyBatis框架大家肯定都用过的,废话我就不再多说了,这篇文章就给大家分享一下有关MyBatis框架底层的执行原理吧(Deb

  • React Hydrate原理源码解析

    目录 引言 Demo ReactDOM.render ReactDOM.hydrate hydrate 过程 事件绑定 hydrate 源码剖析 beginWork HostRoot Fiber HostComponent HostText Fiber tryToClaimNextHydratableInstance completeUnitOfWork popHydrationState prepareToHydrateHostInstance prepareToHydrateHostText

  • go slice 扩容实现原理源码解析

    目录 正文 扩容的示例 实际扩容倍数 growslice 实现 growslice 实现步骤 growslice 源码剖析 总结 正文 基于 Go 1.19. go 的切片我们都知道可以自动地进行扩容,具体来说就是在切片的容量容纳不下新的元素的时候, 底层会帮我们为切片的底层数组分配更大的内存空间,然后把旧的切片的底层数组指针指向新的内存中: 目前网上一些关于扩容倍数的文章都是基于相对旧版本的 Go 的,新版本中,现在切片扩容的时候并不是那种准确的小于多少容量的时候就 2 倍扩容, 大于多少容量

  • axios拦截器工作方式及原理源码解析

    目录 axios 拦截器的配置方式 use() 方法的定义 拦截器如何执行 拦截器回调方法的添加顺序 同步执行请求拦截器(顺序执行) 异步执行请求拦截器(同时执行) Q&A 拦截器是如何工作的 拦截器的执行顺序 同步&异步 axios 拦截器的配置方式 本文所用 axios 版本号为:1.3.2. axios 中有两种拦截器: axios.interceptors.request.use(onFulfilled, onRejected, options):配置请求拦截器. onFulfil

  • 深度剖析java动态静态代理原理源码

    正文 关于Java中的动态代理,我们首先需要了解的是一种常用的设计模式--代理模式,而对于代理,根据创建代理类的时间点,又可以分为静态代理和动态代理. 静态代理 1.静态代理 静态代理:由程序员创建或特定工具自动生成源代码,也就是在编译时就已经将接口,被代理类,代理类等确定下来.在程序运行之前,代理类的.class文件就已经生成. 2.静态代理简单实现 根据上面代理模式的类图,来写一个简单的静态代理的例子.我这儿举一个比较粗糙的例子,假如一个班的同学要向老师交班费,但是都是通过班长把自己的钱转交

随机推荐