在了解Vue的响应式原理之前,建议先去了解一下观察者模式
Vue实现响应式主要依赖于三个类:Observer、Dep、Watcher
。
对于data、props
选项而言,Vue会通过Observer
类的observe
方法(instance/state.js的170行左右)将它们重新构建,具体来说,就是使用ES5里面的Object.defineProperty(...)
方法将其变更为getter/setter
形式,以便于对data、props
选项内的数据进行劫持/监听。
在通过getter
劫持数据的过程中,Vue为每一个data、props
选项内的属性单独创建了Dep
类实例(observer/index.js的170行左右),Dep
类实例是一个调度中心,它会收集订阅了该属性的Watcher
实例,存放在内部队列之中。
在通过setter
监听数据的过程中,Vue会通过Dep
类的notify
方法发布消息(observer/index.js的224行左右),调用内部队列中的所有的Watcher
类实例的update
方法(observer/dep.js的44行左右),进而去重新渲染组件。
每一个Vue组件都会拥有一个对应的Watcher
类实例,我们通常称这个类实例为观察者对象,它收集Vue的render
函数渲染组件时所需要的依赖(其实就是在模板内使用了的props、state的数据),并且提供依赖变更时,Vue组件重新渲染所需要的函数。
Watcher
类实例创建于created
到beforeMounted
阶段(instance/lifecycle.js的206行左右),每当在模板内发现了一个第一次出现的依赖,Watcher
实例都会被推入该依赖对应的Dep
实例的内部队列之中。
以上是Vue实现双向数据绑定的代码实现,以下是其设计思想,可能读起来会有一些别扭。
下图十分明确的阐释了Vue双向绑定数据的整个流程:
首先通过一次渲染操作触发Data的getter(这里保证只有视图中需要被用到的data才会触发getter)进行依赖收集,这时候其实Watcher与data可以看成一种被绑定的状态(实际上是data的闭包中有一个Deps订阅者,在修改的时候会通知所有的Watcher观察者),在data发生变化的时候会触发它的setter,setter通知Watcher,Watcher进行回调通知组件重新渲染的函数,之后根据diff算法来决定是否发生视图的更新。
在Vue中,是不允许动态的为Vue实例添加数据property的,即是说,所有的响应式数据都必须一开始就在状态机data内声明
var vm = new Vue({
data: {
//不设置message
},
template: '<div>{{ message }}</div>' //Vue 将警告你渲染函数正在试图访问不存在的 property。
})
// 动态设置 `message`,
vm.message = 'Hello!'
Vue无法监听复杂数据类型的property的内部变化,例如Array对象的某一项元素不通过数组方法发生改变、Object对象的某一项属性发生变化,Vue的Watcher是无法侦听到的,自然就不会触发视图重绘
Vue 在更新 DOM 时是异步执行的。只要侦听到数据变化,Vue 将开启一个队列,并缓冲在同一事件循环中发生的所有数据变更。如果同一个 watcher 被多次触发,只会被推入到队列中一次。
对上面引用的解释为:Vue处理property变化是异步批量处理的,即同一数据property连续多次变更时,Vue只会把最后一次变更的结果Notify给Watcher,触发视图重绘