学习原文地址:浅探VUE的MVVM模式实现
1、MVVM模式思想
MVVM模式的思想:关注model(数据)的变化,让MVVM框架去自动更新DOM,实现方法主要是数据劫持结合发布订阅模式。
2、核心方法Object.defineProperty
Object.defineProperty是用来数据劫持的关键方法,vue框架是不兼容IE6~8低版本的,主要是因为它的用到了ES5中的这个Object.defineProperty的方法,而且这个方法暂时没有很好的降级方案。
该方法接收三个参数,而且都是必填的。
第一个参数:目标对象。
第二个参数:需要定义的属性或方法的名字。
第三个参数:目标属性所持有的特性。
通过Object.defineProperty这个方法,可以对引用数据实现监听,获取和修改值时分别调用get和set方法。
3、数据劫持
- 先定义初始化构造函数MyVue,通过它来获取我们传进来的数据data和定义的DOM节点范围,然后把data传进定义好的数据劫持方法observe。
- Observe实现了对数据的监听,多写一个observe的小方法用来new Observe,并且在里面做了引用数据类型的判断。这样做的目的是为了方便递归来实现数据结构的深层监听。
- 在设置新值时,也执行了一遍observe方法,是为了实现深度响应,因为在赋值的时候可能会赋值一个引用数据类型的值。
可以看到对我们所定义的data中的数据都已经有了get和set方法了,到这里我们对data中数据的变化都是可以监听的到了。
4、数据代理
下面我们来实现让我们的实例this来代理( _data)数据,从而实现 myvue.name 这样的操作可以直接获取到数据。
以上代码实现了数据代理,思想:就是在构建实例的时候,把data中的数据遍历一遍,依次加到this上,加的过程不要忘记添加Object.defineProperty,只要是数据我们都需要添加监听。
没有实现代理前:
可以通过vm._data访问
不能通过vm.直接访问
实现代理后
5、编译模板(Compile)
我们已经完成对数据的劫持,也实现了this对数据的代理,name接下来做的是怎么把数据编译到我们的dom节点上,也就是在View层展示我们的数据。
以上代码实现我们对数据的编译Compile如下图,可以看到我们把获取到el下面所有的子节点都存储到了文档碎片 fragment 中暂时存储了起来(放到内存中),因为这里要去频繁的操作DOM和查找DOM,所以移到内存中操作。
效果图:
- 先使用while循环,先把el中的所有子节点都添加到文档碎片中;
- 然后通过replace方法,遍历文档中的所有子节点,将他们的文本节点(node.nodeType = 3)带有{{}} 语法中的内容都获取到,把匹配到的值拆分成数组,然后遍历依次去data中查找获取,遍历的节点如果有子节点的话继续使用replace方法,直到反回undefined。
- 获取到数据后,用replace方法替换掉文本中{{}}的整块内容,然后在放回el元素中vm.$el.appendChild(fragment)。
如何实现通过修改数据引发视图的变化呢?涉及到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'是字符串类型的值)
效果图:
函数调用关系