vue源码阅读复盘-watcher模块

作者 : 开心源码 本文共3546个字,预计阅读时间需要9分钟 发布时间: 2022-05-13 共213人阅读

回顾目标

数据驱动视图:

  1. 了解watcher、dep、observer这三个对象之间的关系
  2. 这和VUE对象又有什么关系?
  3. 这和视图又有什么关系?

叙述过程

先彻底了解了一下VUE的简介,并写出了一份建议书。关键词有渐进式、自底向上逐层应用、公告式开发、组件化。

之后理解了观察者模式,观察者模式的初衷是建立低耦合的通信机制,定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动升级。

记得以前看过一本掘金小册,其中作者的结论是:每个vue对象对应一个watcher,每个observer和watcher通过dep建立一对多关系。

而后开始走读代码。大概读了一遍watcher、observer、deb发现没什么新的收获,开始认为每一个vue对象管理一个watcher,而后每个变量都管理一个observer,每个observer管理一个sub,由observer通知watcher,而后渲染。

但是提出了一个关键性的问题,有一个公共的target是用来存放watcher的,发现这个target和依赖关系非常有关,而后启动浏览器用检查器监察这个值,发现这个值一般是null,而后打开vscode,全局查找,找出是什么时候写的这个值,最后定位这个target在watch读数据*的时候被缓存。

这段话非常的重要,只有当执行客户自己设置函数的时候,才会建立依赖关系。

翻看了文档。找到了一段清晰的代码,

var vm = new Vue({      data: { a: 1 },      computed: {        // 仅读取        aDouble: function () {          return this.a * 2        },        // 读取和设置        aPlus: {          get: function () {            return this.a + 1          },          set: function (v) {            this.a = v - 1          }        }      }    })

而后带着上面的问题,去阅读了vue的构造过程,在initState中发现了computed的构造,其中有一个关键的用法:

function initComputed (vm: Component) {    const computed = vm.$options.computed    ...    const userDef = computed[key]    makeComputedGetter(userDef, vm)    ...}    function makeComputedGetter (getter: Function    , owner: Component): Function {        const watcher = new Watcher(owner, getter, noop, {            lazy: true        })    ...    }

对于观察者来说客户定义的函数是getter,也就是对于框架底层来说,程序员写的computed函数,对组件来说是getter。computed实际上是用包装了一下客户定义的函数。可以把他了解成一种特殊的watcher,根据官方的文档,提到了computed有缓存功能,不会升级相同的结果。简单起见我们就把computed当作watcher了解。

而后开始关注这个函数内部发生了什么:

...computed: {        // 仅读取        aDouble: function () {          return this.a * 2        },...

这里读取了this.a。我想起来vue内部的变量都是解决过的。用了observe这个工厂方法加工过,set和get都和watcher和deb耦合。执行aDouble这个函数,computed就作为一个watcher被Dep.target记住了,而a就是一个obsserver,调用了get就会将watcher保存在deps数组中,就如同a调用get就被aDouble盯上(watch)了,下次a变化(调用a.set)就重新执行一遍aDouble。

这个时候我们再回去看wacher对象,发现这个对象有一个cb存放回调函数,而且代码中还出来了属于VNode模块的patch。cb应该是负责重新渲染视图。

所以这即可以解释VUE是如何驱动视图改变的,源自于一种自动升级的一对多机制。这可以处理父子组件的通信问题,父子组件状态同步可以通过向子组件注入父组件的数据引用,可以实现单向数据流。

查看了官方文档,结果发现:

每个组件实例都对应一个 watcher 实例,它会在组件渲染的过程中把“接触”过的数据属性记录为依赖。之后当依赖项的 setter 触发时,会通知 watcher,从而使它关联的组件重新渲染。

证明了掘金那个作者说的没错,每一个vue确实有一个watcher

查看watcher.get的call stack:

get (vue.js:647)Watcher (vue.js:638)Vue.$watch (vue.js:1254)createWatcher (vue.js:1232)initWatch (vue.js:1217)initState (vue.js:1105)Vue._init (vue.js:2110)Vue (vue.js:2150)(anonymous) (app.js:7)

我们看到Vue.$watch说明vue下的确有一个watcher

这个watcher目的是什么呢?我们回去看了一下watch的构造函数

constructor (    vm: Component,    expOrFn: string | Function,    cb: Function,    options?: Object = {}  ) {     ...      this.getter = parsePath(expOrFn)     this.value = this.lazy           ? undefined           : this.get()     ...  }

在parsePath之中会触发所有vue的内部对象的get的函数句柄,而后被执行,所有的内部数据改动都会导致wacher重新渲染数据

评估结果:

结果不等于目标

  1. 开始否认了认为每一个vue对象管理一个watcher
  2. 我们推导出了computed和watcher的关系,并认为这是watch唯一的作用

结果等于目标

  1. 了解watcher、dep、observer这三个对象之间的关系
    每一个客户定义回调函数管理一个watcher,而后每个VUE内部data都管理一个observer,每个observer管理一个sub,由observer通知watcher。
  2. 这和VUE对象又有什么关系
    VUE对象保存了data数据,每一个data都管理一个observer,computed是一种特殊的watcher,VUE对象渲染视图的时候会要求watcher返回一个值,这个时候watcher执行客户定义的函数句柄,也就是computed当中定义的函数时,每个data被读取的时候会和当前的函数建立依赖关系,当这个data升级的时候会重新渲染视图,重新执行客户定义函数。且当new一个vuew对象的时候watcher会监听所有的内部数据对象。
  3. 这和视图又有什么关系
    每个watcher负责自动升级视图。

分析起因

开始否认为每一个vue对象管理一个watcher

  1. 先看了别人的文章而后带着结论去看的代码,最后只是找怎样支持这个结论的代码段。但是发现了文章之外的内容误以为文章错误了。
  2. watcher是被vue对象直接调用的,很容易让人联想一对一关系,但不确定
  3. dep.target作为关键的变量,作搞清楚这个变量的作用,即可以答复谁依赖谁这个问题。

我们推导出了computed和watcher的关系,并认为这是唯一的作用

  1. 当看到watcher.value时。意识到一个vue对象可能需要维护多个watcher,因而原来的假设不成立,那么需要找到watcher具体是做什么的。沿着watcher.get一直往下看,发现了watcher的作用和computed有关联,因而认为computed是watcher的一种体现,而一个vue对象又有n个computed。这才认为作者的观点是错误的。
  2. 很多文章中没有找到关于computed和watcher的作用,误以为是别人了解错了,其实这和我的结论并不冲突。

推演规律

自然语言更加容易被人了解,由于首因效应,人对事物的了解不太容易改变,那么我们看别人的文章,我们了解的不是原作者的设计思想,而是经过了非原作者的几层解释的最终结果。

那么这就导致了我对其余人的结论有一种怀疑,想找出反证的点。而这个时候就需要从官方文档中找到依据,作者和团队的文档最具备权威和可行度。

从非自然语言——代码。总结出来的,我们可以在阅读源代码参考别人的文章,但是其中最有价值的部分,不是别人的结论,而是别人阅读的顺序,看别人是如何抓住主干去了解的。综上有两个东西对阅读源代码有益,分别是文章的行文顺序和文章的大标题,文章的大标题一般是对源代码模块的高度概括。

说明
1. 本站所有资源来源于用户上传和网络,如有侵权请邮件联系站长!
2. 分享目的仅供大家学习和交流,您必须在下载后24小时内删除!
3. 不得使用于非法商业用途,不得违反国家法律。否则后果自负!
4. 本站提供的源码、模板、插件等等其他资源,都不包含技术服务请大家谅解!
5. 如有链接无法下载、失效或广告,请联系管理员处理!
6. 本站资源售价只是摆设,本站源码仅提供给会员学习使用!
7. 如遇到加密压缩包,请使用360解压,如遇到无法解压的请联系管理员
开心源码网 » vue源码阅读复盘-watcher模块

发表回复