目录
1. 什么是双向数据绑定?
数据双向绑定是指:数据的变化驱动视图的更新,视图的变化驱动数据的更新,如图所示:
2. 双向绑定的原理是什么?
Vue是一种双向数据绑定的框架,整个框架由三部分组成:
- 数据层(Model):应用的数据及业务逻辑,是开发者写的业务代码
- 视图层(View):应用的页面展示效果,由页面模板和样式组成
- 业务逻辑层(ViewModel):框架封装的核心,它的主要功能是将数据与视图关联起来
上面这三部分就是所谓的MVVM框架。而关键点就在于业务逻辑层(ViewModel) 部分。
ViewModel的主要职责就是在数据变化之后更新视图,在视图变化之后更新数据。
数据双向绑定原理图:
我们可以看出,数据双向绑定就是通过数据劫持结合发布者-订阅者模式 实现的。
原理: 通过Object.defineProperty
来劫持数据的setter
、getter
,在数据变动时发布消息给订阅者,订阅者收到消息后进行相应的处理。
3. 双向数据绑定的实现
注: 这部分代码不是本人写的,还没有那个水平,代码来自github,个人感觉写的非常不错,地址:链接直达
通过以下四个步骤实现数据数据的双向绑定:
- 实现一个监听器
Observer
,对数据对象的所有属性进行监听,如有变动可拿到最新值并通知订阅者; - 实现一个解析器
Compile
,对每个元素节点的指令进行扫描和解析,根据指令模板替换数据,以及绑定相应的更新函数; - 实现一个发布订阅模型
Watcher
+Dep
,作为连接Observer和Compile的桥梁,能够订阅并收到每个属性变动的通知,执行指令绑定的相应回调函数,从而更新视图。; - 实现一个
MVVM
,整个以上三者,作为一个入口函数;
其中:
-
Dep
是发布订阅者模型中的发布者:get数据的时候,收集订阅者,触发Watcher的依赖收集;set数据时发布更新,通知Watcher 。一个Dep实例对应一个对象属性或一个被观察的对象,用来收集订阅者和在数据改变时,发布更新。 -
Watcher
是发布订阅者模型中的订阅者:订阅的数据改变时执行相应的回调函数(更新视图或表达式的值)。一个Watcher可以更新视图,如html模板中用到的{{test}},也可以执行一个$watch
监督的表达式的回调函数(Vue实例中的watch项底层是调用的$watch
实现的),还可以更新一个计算属性(即Vue实例中的computed项)。
3.1 监听器 Observer 的实现
Vue使用了Object.defineProperty()
方法来劫持数据属性的getter
和setter
。该方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性, 并返回这个对象。
语法: (MDN的相关解释)
Object.defineProperty(obj, prop, descriptor)
参数如下:
- obj:要定义属性的对象。
- prop:要定义或修改的属性的名称或 Symbol 。
- descriptor:要定义或修改的属性描述符
主要注意的是,应当直接在 Object 构造器对象上调用此方法,而不是在任意一个 Object 类型的实例上调用。
数据监听功能实现:
使用Obeject.defineProperty()
来监听属性变动,就需要observe的数据对象进行递归遍历,包括子属性对象的属性,给它们都加上 setter
和getter
:
这样observe的数据监听的功能就实现了,那么下面就来实现一下他的数据变化通知订阅者的功能, 我们只要定义一个数组,来收集订阅者,数据变化时,触发notify来通知所有的订阅者,在调用订阅者的数据更新功能:
这样,数据变化通知订阅者的功能也就实现了。
发布-订阅者设计模式 又叫观察者模式,它定义了对象间的一种一对多的依赖关系,当一个对象的状态改变时,所有依赖于它的对象都将得到通知。
3.2 解析器 Compile 的实现
解析器 Compile 的作用如下:
- 解析模板指令,替换模板数据,然后初始化渲染页面视图
- 将每个指令对应的节点添加监听数据的订阅者,并绑定更新函数
实现步骤如下:
(1)将模板实例拷贝在fragment
文档片段中,减少DOM操作。
(2)编译:调用对应的指令渲染函数进行数据的渲染,并将对应的指令更新函数进行绑定。
(3) 将编译好的文档放在页面中
这样解析器就完成了。
3.3 订阅者 Watcher 的实现
Watcher的使用场景:
- 观察模板中的数据
- 观察创建Vue实例时watch选项里的数据
- 观察创建Vue实例时computed选项里的数据所依赖的数据
订阅者 Watcher 作为解析器和监听器的之间的桥梁,其主要作用是:
- 在自身实例化时往属性订阅器(dep)里面添加自己
- 自身必须有一个
update()
方法 - 属性变动
dep.notice()
通知时,能调用自身的update()
方法,并触发Compile中绑定的回调
实例化Watcher的时候,调用get()
方法,通过Dep.target = watcherInstance
标记订阅者是当前watcher
实例,强行触发属性定义的getter
方法,getter
方法执行的时候,就会在属性的订阅器dep
添加当前watcher
实例,从而在属性值有变化的时候,watcherInstance就能收到更新通知。
3.4 实现一个MVVM
MVVM作为数据绑定的入口,整合Observer、Compile和Watcher三者,通过Observer来监听自己的model数据变化,通过Compile来解析编译模板指令,最终利用Watcher搭起Observer和Compile之间的通信桥梁,实现了数据的双向绑定。
这里主要还是利用了Object.defineProperty()
这个方法来劫持了vm实例对象的属性的读写权,使读写vm实例的属性转成读写了vm._data
的属性值,达到需要的效果。