剖析vue实现原理-如何实现MVVM中的双向数据绑定
1. 了解vue的双向数据绑定原理以及核心代码模块
2. 学习如何实现双向绑定
为了便于说明原理与实现,本文相关代码主要摘自vue源码,并在开始讲解的过程中尽量对代码进行简化改造,等基本实现之后再考虑其他情况(例如开始并未考虑到数组的处理、数据的循环依赖等等)
***
下面的代码就是最后需要实现的功能,语法和vue一模一样,如果看不懂下面代码是什么意思,那请先去学习vue。如果no problem,请继续向下阅读。
```
放置代码的区域
```
```
放置最后实现效果的区域,gif动态图
```
***
端好小板凳,坐直,先听我叭叭叭的讲一些废话,这些废话是后面实现思路的一个很好的铺垫!
MVVM=Model+View+ViewModel
Model和View通讯都是靠着ViewModel这个两面派(中间商)来进行的。
要想搞清楚双向数据绑定,就得先搞清楚哪两个方向,然后再分别搞清楚每个方向如何绑定,最后自然的就搞清楚了双向数据绑定。
双向:
一个方向是:数据 ---> 页面 Model ---> View
一个方向是:页面 ---> 数据 View ---> Model
先说View ---> Model,这个其实很简单,就是一些表单组件上绑定input或者change等事件,当事件被触发时修改Model的数据即可;
再说Model ---> View,这个里面有三个问题需要思考,同时也是三个难点。
1. VM如何知道Model的某个数据发生变化了呢?
2. VM即使知道Model的某个数据发生变化了,那之后又如何通知View重新渲染呢?
3. View收到重新传染的指令之后,又是如何解析dom并重新传染dom呢?
**整理一下实现思路:**
1. 实现监听数据的变化
2. 实现解析DOM模板(即如何实现模板引擎)
3. 实现事件监听
***
叭叭叭到此结束,带着上面的实现思路,我们来一一攻克!!!!!!
# 双向数据绑定
实现思路第一个就是实现监听数据的变化,那这和实现双向数据绑定是等同的吗?我的答案是:yes yes yes!只要实现了这个那就完成50%的工作了。如果你不能理解,请看我下面的辩论:
目前几种主流的MVC(VM)框架都实现了**单向数据绑定**,而正如我上面的解释:我所理解的双向数据绑定无非就是在单向数据绑定的基础上给一些表单组件上(input、textarea、select、checkbox、radio等)绑定input或者change事件,来动态修改model和 view,并没有多么高深。所以无需太过介怀是实现的单向数据绑定或双向数据绑定。
实现数据绑定的做法有大致如下几种:
- 发布者-订阅者模式(backbone.js)
- 脏值检查(angular.js)
- 数据劫持(vue.js)
**发布者-订阅者模式**: 一般通过sub, pub的方式实现数据和视图的绑定监听,更新数据方式通常做法是` vm.set('property', value)`。但是这种方式现在毕竟太low了,我们更希望通过 `vm.property = value`这种方式更新数据,同时自动更新视图,于是有了下面两种方式
**脏值检查**:angular.js 是通过脏值检测的方式比对数据是否有变更,来决定是否更新视图,最简单的方式就是通过`setInterval()`定时轮询检测数据变动,当然Google不会这么low,angular只有在指定的事件触发时进入脏值检测,大致如下:
- DOM事件:例如input,change,click等等(ng-click)
- XHR响应事件 ( $http )
- 浏览器Location变更事件 ( $location )
- Timer事件( $timeout , $interval )
- 执行 $digest() 或 $apply()
**数据劫持**:vue.js 则是采用数据劫持结合发布者-订阅者模式的方式,通过`Object.defineProperty()`来劫持各个属性的setter,getter。在数据变动时发布消息给订阅者,触发相应的监听回调。
# 图解思路
已经了解到vue是通过数据劫持的方式来做数据绑定的,其中最核心的方法便是通过`Object.defineProperty()`来实现对属性的劫持,达到监听数据变动的目的,无疑这个方法是本文中最重要、最基础的内容之一,**如果不熟悉`defineProperty`,请先学习**。
整理了一下,要实现mvvm的双向绑定,就必须要实现以下几点:
1. 实现一个数据监听器Observer
能够对数据对象的所有属性进行监听,如有变动可拿到最新值并通知订阅者
2. 实现一个指令解析器Compile
对每个元素节点的指令进行扫描和解析,根据指令模板替换数据,以及绑定相应的更新函数
3. 实现一个Watcher
作为连接Observer和Compile的桥梁,能够订阅并收到每个属性变动的.通知,执行指令绑定的相应回调函数,从而更新视图
4. 实现Vue入口函数
整合以上三者
下图是我在网上找到的两张图,本人作图能力有限,就给盗用了,希望博主不要怪罪于我
![](https://box.kancloud.cn/11f87024c9a6befdbf931dccb4ef02b0_730x390.png)
![](https://box.kancloud.cn/f87943d59b0bca2de2d8fbe63fc9c324_785x578.png)
所有思路我们都已经整理完毕,并且用两张大图也表述了它们之间的关系,也比较明确相关逻辑和模块功能了,下面就要开始上代码了。当然,我会在代码中加入详尽的注释,以帮助大家理解。
# 具体实现
## 1. 实现Observer
Observer模块的职责就是劫持属性的变化,