Vue中的MVVM(2)--view -> model的绑

作者: 大春春 | 来源:发表于2018-03-01 17:42 被阅读24次

    再次看了上次写的博客关于Vue的MVVM,发现虽然介绍了MVVM的原理,但是感觉还不够详细,现在就再次根据这篇博客写详细一点,来看看new Vue的时候Vue究竟做了些什么事。
    我想,以需求作为出发点来理解原理会比较容易,所以这篇博客会以提出需求 -> 解决需求的方式来写。

    项目地址,欢迎start

    Vue中的MVVM原理介绍

    可以先阅读我的这篇博客了解一下关于Vue的MVVM,另外需要记住这一幅图(很重要),这张图就是本篇博客的概括:

    image.png

    回顾

    继上一篇文章Vue中的MVVM--model -> view的绑定,我们完成了对页面的初始化渲染,达成了如下要求:

    image.png
    image.png
    但还未完成view -> model的绑定,所以不能通过修改数据来触发视图的更新,今天就来完成剩余部分

    提出需求

    继上图:


    image.png

    我们需要做到的是:当修改输入框的数据时,上面的文字也随之进行刷新。

    分析

    先来看看还未完成的部分:

    image.png
    其中包含观察者Observer,监听器Watcher,然后还有一个Dep,过程是:
    1. Compiler中监听数据的变化并绑定监听器;
    2. 在观察者Observer中实现对所有数据的gettersetter
    3. 监听器Watcher把更新事件添加进Dep的事件队列中;
    4. 观察者Observer发现数据产生变化的时候通知Dep
    5. Dep把事件队列中的更新事件全部执行一遍;

    总结下来就是实现两个事情:

    1. 添加数据依赖;
    2. 触发数据变更事件;
      接下来就先创建WatcherObserverDep三个类;
      image.png
      image.png
      image.png

    先来看看Dep是什么

    根据上面的的步骤描述,很容易感觉到Dep像是一个容器,存储着对视图的更新事件,是的,这是一个发布订阅模式的实现,该模式包含事件队列subs,添加事件方法addSub,执行事件队列函数notify,移除事件队列里的事件removeSub

    image.png
    看完发布订阅模式后,继续我们的流程。

    添加依赖

    • 在Compiler中监听数据的变化并绑定监听器
      在上一篇对model -> view的绑定中我们有一个针对数据和指令统一进行绑定的方法bind,为了不和后面的v-bind指令冲突,现在改为了bindData;

      image.png
      在这个函数承载的功能有获取tag文本和执行视图的更新,所以我们可以在这个函数中添加对数据的监听器Watcher,因为后面需要把更新视图的事件添加进Dep中,所以Watcher中需要的参数要有一个更新事件也就是更新器updater中的视图更新函数,此外将当前vm实例和得到的data键值也传进去备用;
      compile中添加数据监听器
      更新器
      watcher
      接着
    • 在观察者Observer中实现对所有数据的getter和setter
      注意这一步需要考虑到数据中含有嵌套的对象,需要进行递归操作才能全部添加gettersetter,使用的是Object.definedPropertyObserver接受的参数是data对象:

      image.png
      然后在MVVM类中代入data并执行observer:
      image.png
      接着对所有data中的属性绑定gettersetter,这一步需要进行递归操作:
      image.png
      最后回到Compiler中,实现视图对数据的修改:
      比如在输入框中修改数据直接反应到data
      image.png
      image.png
      来看看成果:
      image.png
      这个时候在视图上对数据进行的修改就可以反映到data上,并触发该数据的setter函数;
    • 监听器Watcher把更新事件添加进Dep的事件队列中;
      这一步需要考虑一个问题:在什么时候怎么样把更新事件添加到Dep中去?
      回顾上面所写的,data中的每一个属性都有一个对应的Watcher,可以在Watcher中获取得到对应的data中的属性。那么在这个获取的过程中,又会触发该属性的getter,就可以考虑在该属性的getter中添加,分解成一下步骤就是:
      ① 把这个Watcher通过构造函数本身的属性target保留在Dep中,然后去data中取值;

      image.png
      ② 取值的时候触发Observer中该属性的getter,在Observer中new一个Dep实例出来,判断如果Dep类的target非空(也就是该属性已被有监听器),则触发依赖添加事件depend;
      image.png
      ③ 这时候的Dep.target就是被监听属性的Watcher,在Dep类中添加一个方法depend,用来把该属性的Watcher添加进事件队列subs中,但是这一步要当前的Watcher,需要在Watcher类中进行触发,所以在Watcher中创建一个函数addDep,把Dep的实例作为参数放进去,然后在addDep中进行更新事件的添加:
      image.png
      image.png
      然后置空Dep.target,用于下一个数据的依赖添加
      image.png
      现在我们来看看subs中有些什么
      image.png
      可见msg被引用了两次就被监听了两次,这时候只要当msg这个数据发生变化并触发setter时,将subs中所有的watcher实例里的更新回调update拉出来执行即可
    • 更新视图

    1. 更新视图的时候,我们先要获取当前的数据新值,然后作为参数放进回调函数中,并且还要对新的数据进行上面的依赖添加步骤,那么Watcher还需要一个update函数用来统一做这个事:
      image.png
    2. Observersetter中触发Depnotify方法,进行视图的更新:
      image.png
    3. 到了这步其实就已经达成效果了:


      image.png
    • 修复bug
      虽然MVVM双向绑定的功能已经达成,但是还是有不少bug的,其中最严重的有两个
    1. 当我们多次更新数据的时候,会发现添加进subswatcher发生了递增的现象,所以当快速更新数据时就会导致执行函数过多而页面崩溃;

      image.png
      造成这个现象的原因是在进行第一次的更新时,watcher将同一个数据的新值也进行了依赖添加,也就是let newVal = this.get()这一段;
      image.png
      既然知道了原因,那么解决起来也很简单,给每一个被监听的对象都添加一个id即可。
      因为添加sub的操作是在Watcher中进行的,所以在Watcher中创建一个对象depIds
      image.png
      然后给每一个Dep都添加一个不同的id
      image.png
      最后在Watcher中判断depIds是否已经有这个id的Dep实例存在,如果没有则添加进去并执行addSub,否则不执行:
      image.png
      效果,无论怎么修改,都只会有固定数量的Watcher存在:
      image.png
    2. 当修改数据为对象的时候,这个对象没有进行监听,这个也好解决,只要在setter中进行判断即可,若为对象则针对该对象重新进行监听

      image.png

    总结

    到这里为止,我们就完成了view -< model的绑定,并且知道在new Vue的时候大致做了一些什么事了,剩下的就是逐步完善,例如对更多指令的支持,对methods以及computedwatch的支持。

    相关文章

      网友评论

        本文标题:Vue中的MVVM(2)--view -> model的绑

        本文链接:https://www.haomeiwen.com/subject/qnlgxftx.html