Vue2.x学习(二)Vue的MVVM模式

913 阅读4分钟

学习原文地址:浅探VUE的MVVM模式实现

1、MVVM模式思想

MVVM模式的思想:关注model(数据)的变化,让MVVM框架去自动更新DOM,实现方法主要是数据劫持结合发布订阅模式。


2、核心方法Object.defineProperty

Object.defineProperty是用来数据劫持的关键方法,vue框架是不兼容IE6~8低版本的,主要是因为它的用到了ES5中的这个Object.defineProperty的方法,而且这个方法暂时没有很好的降级方案。


该方法接收三个参数,而且都是必填的。

第一个参数:目标对象。

第二个参数:需要定义的属性或方法的名字。

第三个参数:目标属性所持有的特性。

  • value:属性的值。
  • writable:如果为 false ,属性的值就不能被重写,只能为只读了。
  • enumerable:是否可枚举,默认是false不可枚举的(通常设置为true)
  • configurable:总开关,一旦为false,就不能再设置其他的(value,writable, enumerable)
  • get():函数,获取属性值时执行的方法(不可以和writable、value属性共存)
  • set():函数,设置属性值时执行的方法(不可以和writable、value属性共存)

  • 通过Object.defineProperty这个方法,可以对引用数据实现监听,获取和修改值时分别调用get和set方法。

    3、数据劫持


    1. 先定义初始化构造函数MyVue,通过它来获取我们传进来的数据data和定义的DOM节点范围,然后把data传进定义好的数据劫持方法observe。
    2. Observe实现了对数据的监听,多写一个observe的小方法用来new Observe,并且在里面做了引用数据类型的判断。这样做的目的是为了方便递归来实现数据结构的深层监听。
    3. 在设置新值时,也执行了一遍observe方法,是为了实现深度响应,因为在赋值的时候可能会赋值一个引用数据类型的值。
    看看有没有实现数据劫持



    可以看到对我们所定义的data中的数据都已经有了get和set方法了,到这里我们对data中数据的变化都是可以监听的到了。

    4、数据代理

    数据代理,我们用过vue的都知道,在实际使用中是能直接通过实例+属性(vm.name)直接获取到数据的,而我们上面的代码要获取到数据还需要这样myvue._data.name这样来获取到数据,中间多了一个 _data 的环节,这样使用起来不是很方便的。


    下面我们来实现让我们的实例this来代理( _data)数据,从而实现 myvue.name 这样的操作可以直接获取到数据。


    以上代码实现了数据代理,思想:就是在构建实例的时候,把data中的数据遍历一遍,依次加到this上,加的过程不要忘记添加Object.defineProperty,只要是数据我们都需要添加监听。

    没有实现代理前:

    可以通过vm._data访问



    不能通过vm.直接访问



    实现代理后



    5、编译模板(Compile)

    我们已经完成对数据的劫持,也实现了this对数据的代理,name接下来做的是怎么把数据编译到我们的dom节点上,也就是在View层展示我们的数据。


    以上代码实现我们对数据的编译Compile如下图,可以看到我们把获取到el下面所有的子节点都存储到了文档碎片 fragment 中暂时存储了起来(放到内存中),因为这里要去频繁的操作DOM和查找DOM,所以移到内存中操作。

    效果图:


    1. 先使用while循环,先把el中的所有子节点都添加到文档碎片中;
    2. 然后通过replace方法,遍历文档中的所有子节点,将他们的文本节点(node.nodeType = 3)带有{{}} 语法中的内容都获取到,把匹配到的值拆分成数组,然后遍历依次去data中查找获取,遍历的节点如果有子节点的话继续使用replace方法,直到反回undefined。
    3. 获取到数据后,用replace方法替换掉文本中{{}}的整块内容,然后在放回el元素中vm.$el.appendChild(fragment)。
    6、关联视图(view)与数据(model)

    如何实现通过修改数据引发视图的变化呢?涉及到js中的常用的js设计模式--发布订阅模式。

    发布订阅模式的实现


    这里用Dep方法来实现订阅和通知,在这个类中有addSub(添加)和notify(通知)两个方法,我们把将要做的事情(函数)通过addSub添加进数组里,等时机一到就notify通知里面所有的方法执行。

    通过watcher创建的函数都会有一个update执行的方法可以方便我们调用。


    把定义函数的方法watcher加到了replace方法里面,但是这里的watcher跟刚写编写的多了两个形参vm、RegExp.$1,而且写法也新增了一些内容,因为当new Watcher的时候会引发发生几个操作,来看完整代码:





    1)首先看在Watcher构造函数中新增了一些私有属性分别代表:

    • Dep.target = this(在构造函数Dep.target临时存储着watcher的当前实例)
    • this.vm = vm(vm = myvue实例)
    • this.exp = exp(exp = 匹配的查找的对象'obj1.name'是字符串类型的值)
    我们存储这些属性后,接下来就要去获取用exp匹配的字符串里面对于数据也就是 vm.a.b,但是此时的exp是个字符串,你不能直接这样取值vm[a.b]这是错误的语法,所以要循环去取到对应的值。


    总而言之,就是在每个数据操作的部分,添加数据更新的功能,来实现数据的改变,使得view跟着改变。

    效果图:


    函数调用关系