Ⅰ vue数据双向绑定原理
vue.js 采用数据劫持结合发布者-订阅者模式的方式,通过 Object.defineProperty() 来劫持各个属性的setter,getter,在数据变动时发布消息给订阅者,触发相应的监听回调。
首先我们为每个vue属性用Object.defineProperty()实现数据劫持,在监听数据的过程中,为每个属性分配一个订阅者集合的管理数组dep;然后在编译的时候在该属性的数组dep中添加订阅者 watcher,v-model会添加一个订阅者,{{}}也会,v-bind也会,只要用到该属性的指令理论上都会,接着为input会添加监听事件,修改值就会为该属性赋值,触发该属性的set方法,在set方法内通知订阅者数组dep,订阅者数组循环调用各订阅者的update方法更新视图。
实现步骤:修改输入框内容 => 在事件回调函数中修改属性值 => 触发属性的 set 方法=>发出通知 dep.notify() => 触发订阅者的 update 方法 => 更新视图。
流程图 :
在实例化一个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方法,更新模板的绑定数据。
observer 模块共分为这几个部分:
示意图如下:
Observer的构造函数
value是需要被观察的数据对象,在构造函数中,会给value增加 ob 属性,作为数据已经被Observer观察的标志。如果value是数组,就使用observeArray遍历value,对value中每一个元素调用observe分别进行观察。如果value是对象,则使用walk遍历value上每个key,对每个key调用defineReactive来获得该key的set/get控制权。
Dep是Observer与Watcher之间的纽带,也可以认为Dep是服务于Observer的订阅系统。Watcher订阅某个Observer的Dep,当Observer观察的数据发生变化时,通过Dep通知各个已经订阅的Watcher。
Watcher是用来订阅数据的变化的并执行相应操作(例如更新视图)的。Watcher的构造器函数定义如下:
参数中,vm表示组件实例,expOrFn表示要订阅的数据字段(字符串表示,例如a.b.c)或是一个要执行的函数,cb表示watcher运行后的回调函数,options是选项对象,包含deep、user、lazy等配置。
Object.defineProperty(obj, prop, descriptor) ,这个语法内有三个参数,分别为 obj (要定义属性的对象) prop (要定义或修改的属性的名称或 Symbol ) descriptor (要定义或修改的属性描述符=>具体的改变方法)
简单地说,就是用这个方法来定义一个值。当调用时我们使用了它里面的get方法,当我们给这个属性赋值时,又用到了它里面的set方法;
主要解释第三个参数 {
value: 设置属性的值
writable: 值是否可以重写。true | false
enumerable: 目标属性是否可以被枚举。true | false (就是能不能被遍历出来)
configurable: 目标属性是否可以被删除或是否可以再次修改特性 true | false
set: 目标属性设置值的方法
get:目标属性获取值的方法
}
set 是一个函数,接收一个新值,会在值被重写或修改的时候触发这个函数
get 是一个函数,返回一个值,会在属性被调用的时候触发。
注 :
Object.defineProperty()详解
Object.defineProperty()官方文档
已经了解到vue是通过数据劫持的方式来做数据绑定的,其中最核心的方法便是通过Object.defineProperty()来实现对属性的劫持,那么在设置或者获取的时候我们就可以在get或者set方法里假如其他的触发函数,达到监听数据变动的目的。
我们知道通过Object.defineProperty()可以实现数据劫持,它的属性在赋值的时候触发set方法,
当然要是这么粗暴,肯定不行,性能会出很多的问题。
observer用来实现对每个vue中的data中定义的属性循环用Object.defineProperty()实现数据劫持,以便利用其中的setter和getter,然后通知订阅者,订阅者会触发它的update方法,对视图进行更新。
为什么要订阅者 :在vue中v-model,v-name,{{}}等都可以对数据进行显示,也就是说假如一个属性都通过这三个指令了,那么每当这个属性改变的时候,相应的这个三个指令的html视图也必须改变,于是vue中就是每当有这样的可能用到双向绑定的指令,就在一个Dep中增加一个订阅者,其订阅者只是更新自己的指令对应的数据,也就是v-model='name'和{{name}}有两个对应的订阅者,各自管理自己的地方。每当属性的set方法触发,就循环更新Dep中的订阅者。
订阅发布模式(又称观察者模式)定义了一种一对多的关系,让多个观察者同时监听某一个主题对象,这个主题对象的状态发生改变时就会通知所有观察者对象。
发布者发出通知 => 主题对象收到通知并推送给订阅者 => 订阅者执行相应操作
举个例子:
2.实现compile: compile的目的就是解析各种指令称真正的html。
这样一来就实现了vue的数据双向绑定。
参考链接:
理解VUE双向数据绑定原理和实现---赵佳乐
Vue的双向数据绑定原理
vue双向绑定原理分析
Vue原理解析之observer模块
深入响应式原理