1.目前双向数据绑定的方法
发布者-订阅者模式(backbone.js)
因为本文研究的不是这个,所以不做详细解释。
脏检查(Angular.js)
angular只在指定的事件触发时进入脏值检测,例如:DOM事件、XHR响应事件、location变更事件、timer事件、执行$digest()/$apply()
数据劫持(vue)
vue是采用数据劫持$发布者-订阅者结合的方式,来做数据绑定,核心就是Object.defineProperty(),劫持各个属性的getter和setter,在数据模型变化的时候,发布消息给订阅者(绑定了数据模型的DOM元素),触发相应的监听回调。
2.简单例子实现数据绑定
obj.name根据输入框值得变化改变,视图会根据obj.name的改变进行更新。这就是简单的实现了view=>model和model=>view的双向数据绑定。这是vue的基本原理,但是在vue 中并不是这么实现的。
3.vue实现数据绑定的能分解步骤
(1)要将视图模型和数据模型绑定起来
(2)视图变化时,触发数据模型变化
(3)数据模型变化时,触发视图变化
4.流程图
在实例化一个Vue对象的时候,会传进去一个data对象,之后分成两个进程,一个进程是对挂载目标元素模板里的v-model和{{ }};两个指令进行编译。另一个进程是对传进去的data对象里面的数据进行监听。
上图中,observe是利用Object.defineProperty()对传入的data对象进行数据监听,在数据改变的时候触发该属性的set方法,更新该属性的值,并发布消息,我(该属性)的值变了。
compile是编译器,找到vue的指令v-model所在的元素,将data中该属性的值赋给元素的value,并给这个元素添加二级监听器,在元素的值改变的时候,将新值赋给data里面同名属性,这个时候就完成了单向数据绑定,视图 >> 模型。
那么最终的由模型到视图的更新,依赖于dep和watcher,dep会收集订阅者,就是绑定了data里面属性的元素,在数据更新的时候,会触发该属性的set方法,在set里触发该属性的消息发布通知函数。而Watcher根据收到的数据变化通知,更新相应的数据。
dep这个东东给大家解释一下,就是data里的每个属性都有一个dep对象,dep对象里可以有很多订阅者(watcher),但是只有一个添加订阅者的方法和一个发布变化通知的方法,就是模板上可以有多处元素绑定data里的同一个属性值,所以dep是依赖于data里面的属性的。
而Watcher是每个{{ }}有一个,初次编译的时候,会在new的时候自动更新一下模板的数据,等到下次数据改变的时候,由dep通知数据更新,直接调用watcher的update方法,更新模板的绑定数据。
5.new Vue()都干了什么
上面是正常在构建一个vue项目的时候,实例化一个vue。
实例化一个Vue的两个进程,就是数据监听和模板编译。observe(data, obj)传入两个参数,一个是数据模型data,一个是vue的实例vm。
而nodeToFragment方法就是识别模板里面的vue指令进行相应的处理,处理之后的template插入到挂载目标元素里。
6.Object.defineProperty(obj, key, options)
先讲一下这个方法,为下面的数据监听做铺垫。
很简单,该方法接受三个参数,全部都是必填的。参数:
obj:目标对象
key:属性或者方法的名字
options:目标属性所拥有的特性
主要解释第三个参数 {
value:属性的值,
writable:布尔值,规定属性是否可重写,
configurable:总开关,以第一次设置为准,一旦是false,则其他属性不可设置,
enumerbale:决定属性是否可枚举
get:重点,后面讲,
set:重点,后面讲
}
因为如果没有明确的设置其他值,默认都是false。(可以这么理解)。
configurable 只能设置一次,第二次设置会报错。
writable 在值为true的情况下,才能对该值进行重写修改。
enumerable 定义属性是否可枚举 ,就是能不能被遍历出来。
访问器:(set\get)不能和writable/value同时设置 就是会冲突的意思,set和writable都是设置属性值的,所以会冲突 而get和value都是获取属性值的,所以也会冲突(可以先这么理解)
so:一山不容二虎,要么用访问器,要么用writable和value.
set 是一个函数,接收一个新值,会在值被重写或修改的时候触发这个函数
get 是一个函数,返回一个值,会在属性被调用的时候触发。
7.数据监听
利用上面的方法,将data里面所有的属性值遍历一遍,添加上set和get访问器,这样在设置data的属性值的时候,会触发set方法,那么set方法主要有两个作用,一是改变data里面的属性值,二是发出数据变化的通知。
上面的方法中,为每一个属性值绑定一个dep对象,初次调用会有Dep.target,当然这是在Watcher里面进行调用,暂且不谈。属性的两个方法,一个get和一个set,数值改变的时候,首先替换掉旧值,再进行数据变化通知,因为变化通知的是订阅这个属性的元素,所以将订阅这个属性的管理对象dep传进去就好了。
通知函数调用了当前dep对象的notify函数,来通知该dep管理的所有订阅者。至此实现了data数据的监听。
8.模板编译
浏览器不认识vue指令,所以要对模板里面的vue指令进行编译,那么先要获取花在目标,将里面的所有节点劫持下来,在新建的文档碎片(documentflagment)里面进行编译,编译完成自后再插入到挂载目标节点。
如果挂载目标元素有子节点,那么对第一个子节点进行编译(PS:vue规定template标签里只能有一个子元素哦,所以页面的构建要由一个标签包裹起来,在放入template里面)
模板编译器函数会判断元素节点是什么类型,表单元素和文本节点分别处理,因为文本节点的value会由用户手动输入,所以要给表单元素添加监听器,根据输入的value改变data里的属性值,再更新到视图上,这是双向的数据流。而文本节点,只要改变data里面的值,节点随着改变就行,只是单向的数据流。
9.数据和节点绑定
因为dep数组里存的全是watcher对象,所以在dep发布变更通知的时候,会调用该watcher的update方法,来更新该watcher对应的节点值。
watcher原型里面的update方法,会在new Watcher()和dep.notify()的时候调用。so:数据监听和模板编译就是通过watcher连接起来的。
各个函数的位置需要定位好,不然会出现xxx未定义或者xxx不是一个函数。可能我还有没讲明白的地方,我虽然明白,但是不知道怎么讲了,可以评论问我。
最后附上我写的源码:https://github.com/qianluoluo/vueOrigin