数据绑定
上几节完成了对视图的初始化, 但就目前来说都仅仅是静态的, 在改变其 data 内的数据时, 视图还不能实时更新, 所以接下来就是比较复杂的数据绑定
数据绑定是当我们更新某一个数据时, 其在视图上引用或间接引用的数据能够实时的被更新
那么如何去实现数据绑定呢?
先来看看第二章我放的图, 在上面那条线中我们可以看到通过劫持监听属性的变化来通知监听者更新视图, 通俗来说就是把数据的改变, 在 setter 的时候就给截停了, 然后做一些处理再通知视图做更新. 当然底层不可能这么简单, 在最开始时我们将 data 中的数据代理到了 Mvvmvue, 数据的一切的改变都会先走 MvvmVue 中的 setter 或 getter, 然后在 setter 或 getter 中再去对 data 中的某个数据进行更新.
所以 MvvmVue 仅仅是数据代理, 而最终做数据处理的还是 data 中的 setter 或 getter
原理图
也就是说如果要实现数据绑定, 需要通过数据劫持去做
Observer
已经清楚了需求, 先对 data 中的所有数据进行数据劫持
在 MvvmVue 中添加 observe 函数调用, 将 data 传递过去
- //dataProxy.JS
- // 在 MvvmVue 函数中将数据传给 observe
- function MvvmVue(options) {
- this.$options = options // 得到传过来的配置
- var data = this._data = this.$options.data // 得到配置里面的 data 对象
- var _self = this // 保存 this 对象
- // 遍历属性对象 JSON
- Object.keys(data).forEach(function (key) {
- // 实现属性代理
- _self._proxy(key)
- })
- // 进行所有数据监听
- observe(data)
- // 编译 html 模板
- this.$compile = new Compile(_self.$options.el || document.body, _self)
- }
新建一个 observer.JS 文件
定义一个 Observer 类, 主要是对数据做监听
- // observer.JS
- // 检查数据是否符合监测对象
- function observe (data){
- // value 必须是对象, 因为监视的是对象内部的属性
- if (!data || typeof data !== 'object') {
- return
- }
- // 使用 Observer 对象, 处理数据
- new Observer(data)
- }
- // 定义 Observer 对象
- function Observer(data) {
- this.data = data
- this.start(data) // 对数据进行监视
- }
- // 扩展 Observer 函数
- Observer.prototype = {
- // 开始对数据进行监测
- start(data){
- var _self = this
- // 对该对象进行遍历
- Object.keys(data).forEach(function (key) {
- // 拿到 key 值和 value 值, 进行监测
- _self.convert(key,data[key])
- })
- },
- convert(key, value){
- // 对指定属性实现响应式数据绑定
- this.defineReactive(this.data, key, value);
- },
- defineReactive(data, key,val){
- // 间接性的递归, 监测更深层次的数据
- var childObj = observe(val);
- // 给 data 重新定义属性(添加 set/get)
- Object.defineProperty(data,key,{
- enumerable: true, // 可枚举
- configurable: false, // 不能再 define
- get(){
- return val
- },
- set(newVal){
- if(newVal === val){
- return
- }
- val = newVal
- // 如果新值是一个对象的话就再进行监测
- childObj = observe(newVal)
- }
- })
- }
- }
上面的代码对 data 中所有数据进行了 getter 和 setter 监听, 在这里需要注意的一点是, 为了拿到 data 中深层次的对象, 通过类似于递归 (但只是对数据进行处理并不是真正的递归) 的方式去监视更深层次的数据, 其他代码都比较简单就不做过多的赘述
我们能够看到在_data 中也声明了有 setter 和 getter
10.PNG
Dep 以及 watcher
9.PNG
如图画圈和箭头的是已经完成的, 对于 dep 和 watcher 对象是什么, 它是做什么的, 需要深入的去解析
从图上来看, 在 observer 监听中如果有改变就会去通知 Dep 数据有变化, 在 Dep 中数据有变化就会去通知 watcher 数据有变, 然后 watcher 再去进行一个视图更新
另外还有一个步骤是从 Compile 编译阶段到 watcher, 这是需要把模板上所有指令绑定的属性值在 watcher 中做一个存储, 接着再去 dep 对象中添加订阅者也就是将多个 watcher 存储到 dep 中去, 然后如果有更新就通过 dep 去发布通知多个 watcher 有更新
Dep 是什么?
Dep 作为一个发布订阅中的发布者, 肩负的是当数据有更新就发布个通知, 通知 watcher 更新视图...
从代码层面看, data 数据中每一个数据对象, 包括其更深层次的所有对象, 每一个对象即对应一个 Dep, 其作用就是如果其中某一个数据对象发生了改变, 那么其对应的 Dep 对象就应该被告知数据被改变
- // 例如
- data:{
- x:0, // 对应 Dep1 对象
- y:{ // 对应 Dpe2 对象
- z:3 // 对应 Dep3 对象
- }
- }
watcher 是什么?
如上, watcher 作为一个订阅者, 在收到 dep 发布的通知时, 会立刻去更新视图
从代码层来看, watcher 对象的创建时机应该是在模板编译阶段, 在对指令或是双括号进行编译解析时, 会得到一些数据对象, 这时每一个指令或双括号都应该对应一个 watcher 对象, 用来监听数据的更新
- // 例如
- {
- {
- name
- }
- } // 对应一个 watcher 对象
- {
- {
- fruit.apple
- }
- } // 对应一个 watcher 对象
- v-text='name' // 对应一个 watcher 对象
- @click='change' // 对应一个 watcher 对象
Dep 和 watcher 的关系
一个 Dep 对象可能存储多个 watcher 对象, 这是因为一个数据可能用在多个指令或模板内, 如上 {{name}} 和 v-text='name', 那么 Dep 就需要通知多个 watcher 进行更新
一个 watcher 对象可能存储多个 dep 对象, 这是因为可能会出现如上面 fruit.apple 这样的情况, 在 Dep 是什么中 有写, 每一个数据对象即对应一个 Dep 对象, 这时需将 fruit.apple 拆成 fruit 和 apple 两个 dep 来看, 那么就需要在 fruit 和 apple 的 Dep 中分别去存储这个 watcher, 这样不管是 fruit 更新, 还是 apple 更新, 都会去触发这个 watcher 去更新, 避免了该对象的上层更新而没有通知到下一层对象的更新
目前理清楚了 Dep 和 Watcher 是什么以及其复杂的关系, 那么在 Dep 对象中应该有通知和存储 watcher 功能, 在 Watcher 对象中有存储 dep 和视图更新功能, 接下来就可以开始写代码了
其他文章导航:
前端 MVVM 理论 - MVC 和 MVP
前端 MVVM 理论 - MVVM
前端 MVVM 实战 - 常用的几个方法和属性
前端 MVVM 实战 - 数据代理
前端 MVVM 实战 - 模板解析之双括号解析
前端 MVVM 实战 - 模板解析之事件指令和一般指令
前端 MVVM 实战 - 数据绑定(一)
前端 MVVM 实战 - 数据绑定(二)
来源: http://www.jianshu.com/p/3bf0b4d76611