原文:https://github.com/DMQ/mvvm (包含原文源码)
由于源码的注释比较少,我自己加了注释的地址:https://github.com/zengqingxiao/MVVM/tree/master
双向数据绑定方法
- 发布者-订阅者模式 - 微信小程序, React
- 脏值匹配 – Angular
- 数据劫持 – vue
发布者-订阅者模式
一般通过方法实现数据和视图的绑定监听,更新数据通常做法为vm.set(‘property’, value)
脏值匹配
angular.js 是通过脏值检测的方式比对数据是否有变更,来决定是否更新视图,最简单的方式就是通过 setInterval() 定时轮询检测数据变动,当然Google不会这么low,(用一些监听)angular只有在指定的事件触发时进入脏值检测,大致如下
- DOM事件,譬如用户输入文本,点击按钮等。( ng-click )
- XHR响应事件,发生请求 ( $http // Angular中的api)
- 浏览器Location变更事件 ( $location // Angular中的api)
- Timer事件( $timeout , $interval // Angular中的api)
- 执行 $digest() 或 $apply() // Angular中的方法
数据劫持
vue中数据劫持解析图
Observer:数据监听器,能够对data中全部属性进行监听,如果数据有变动可以拿到最新值,并且通知订阅者(Watcher)
通过 Object.defineProperty 设置为 存储描述符
Compile: 指令解析器,对元素上的指令和{{}}中的文本进行扫描和解析,根据指令模板替换数据,以及绑定相应的更新函数
Watcher:订阅器,作为连接Observer和Compile的桥梁
- 在初始化的时候通过Compile(解析指令)来绑定和订阅器Dep的关系
- 在数据变化的时候可以更新订阅者,或者接收到Dep发送变化,从而更新视图
大概流程
- 执行Observer函数(数据监听器)对data中的全部属性进行监听,并且对其用Object.defineProperty中的存储描述符来添加get()和set()方法,并且构建了相关的dep对象,使其如果data中的数据发生修改那么就会通知Dep(但是在开始阶段Dep还是没有创建的,在后续创建)
- 执行Compile(指令解析器):来初始化页面对node中的文本节点和元素节点进行解析,通过Updater方法来对node进行渲染,
- 在初始化的过程中我们对那些文本节点和元素(指令)节点进行new Watcher构造函数进行,构造相关Watcher对象(包括了当前表达式exp对应的值,和vm对象,和回调函数),这里的回调函数是Vue对指令或{{}}的语法糖的函数执行,同时对当前文本节点或者指令节点的表达式进行了执行一遍
-
/** * {{}} 文本实例 -->这个只是大概的展示 并不是源码内容 * @param {*} node // 当前文本节点 * @param {*} value // 需要赋值的值 */ textUpdater: function (node, value) { node.textContent = typeof value == 'undefined' ? '' : value; // 对节点的textContent赋值 },
- 而在构架watcher的时候我们会对表达式中的值读取一遍,为什么呢?(例如下面的例子{{a.b.c}}) 其实就是为了触发我们前面在observer中对每一个data设置的访问器属性get,作用就是把当前watcher,通过执行原型链上的方法将其watcher放入到相关联的Dap的subs数组中(也就是说subs数组中放的是watcher),那么触发set访问器函数的时候就会找到相关Dep中的subs数组
- 那么我们每一个data就有一个Dep,而这个Dep中又包含了subs数组,subs数组中包含了Watcher,当某一个值发生改变遍历subs,找到和data相关的watcher,而这个watcher在触发他相关的回调函数,并且由于watcher中包含了vm对象因此可以通过就会去找到相关的node去和改变表达式的值
简单概述Dep
data每一个值都有一个Dep,Dep中有subs数组,其包含了Watcher,所以当某一个data数据发生了改变就会遍历Dep中的subs数组中的Watcher,通过Wathcer中包含了(包括了当前表达式exp对应的值,和vm对象,和回调函数),所以可以通过vm对象找到相关Dom去触发回调函数来改变相关值
注意
例如一个表达式{{A.B.C}}我们表达式只是获取c的值但是这个c这个值相关的watcher分别会放入到a,a.b,a.b.c的Dpe中的subs数组中,主要当a修改那么a.b,a.b.c相关的watcher就会被触发,那么相关的回调函数触发修改node,那么vue是如何做到的呢,其实就是我们遍历如果要获取a.b.c要首先获取到a的值,在获取到a.b的值,那么在获取对应值的时候触发了get()函数,就会把当前表达式{{a.b.c}}所关联的Watcher放入到对应的Dep的subs数组中
getVMVal: function () {
var exp = this.exp.split('.');
var val = this.vm._data; // 初始为data 如果exp为a.b.c 那么过程为 data.a data.a.b data.a.b.c
exp.forEach(function (k) {
val = val[k];
});
return val;
}