前言
Vue最具特色的一点,就是数据驱动,即你不用写复杂的DOM
操作,只需专心于业务所用到的数据。
那么,Vue的数据响应式到底是怎么实现的呢?
正文
数据变化的过程
- 侦测数据的变化 (数据劫持 / 数据代理)
- 收集视图依赖了哪些数据 (依赖收集)
- 数据变化时,自动“通知”需要更新的视图部分,并进行更新 (发布订阅模式)
追踪数据
在 new Vue()
后, Vue 会调用 _init
函数进行初始化,也就是init 过程,在 这个过程Data
通过Observer
转换成了getter/setter
的形式,来对数据追踪变化,当被设置的对象被读取的时候会执行getter
函数,而在当被赋值的时候会执行setter
函数。
- 也就是说,当创建Vue实例时,Vue会将这个实例的
data
对象转换成getter/setter
形式,这对用户是不可见的。 - 所以就出现了无法进入响应式系统的数据,稍后介绍。
收集依赖
每个组件实例都对应一个 watcher
实例,它会在组件渲染的过程中把“接触”过的数据property
记录为依赖。之后当依赖项的 setter
触发时,会通知watcher
,从而使它关联的组件重新渲染。
- 当外界通过
Watcher
读取数据时,会触发getter
从而将Watcher
添加到依赖中。
订阅者
主要作用是用来存放 Watcher
观察者对象
class Dep {
constructor () {
/* 用来存放Watcher对象的数组 */
this.subs = [];
}
/* 在subs中添加一个Watcher对象 */
addSub (sub) {
this.subs.push(sub);
}
/* 通知所有Watcher对象更新视图 */
notify () {
this.subs.forEach((sub) => {
sub.update();
})
}
}
- 在修改对象的值的时候,会触发对应的
setter
,setter
通知之前依赖收集得到的Dep
中的每一个Watcher
,告诉它们自己的值改变了,需要重新渲染视图。这时候这些Watcher
就会开始调用update
来更新视图。
图解全过程
响应式注意项
- 对于对象来说
- 添加或者删除属性,那么这个属性是无法进入响应式系统的
- 覆盖对象是可以进入响应式系统的
- 解决方法可以用
Vue.set()
或是他的别名$set()
const app = new Vue({
el: '#app',
data: {}
})
app.msg = 'hello' //不是响应式
app.$set(app, 'msg', 'hello') //响应式
- 对于数组来说
- 改变数组的长度
- 根据索引值直接赋值
const app = new Vue({
el: '#app',
data: {
arr: [1, 2, 3, 4]
}
})
app.arr[1] = 3333 //不是响应式
app.$set(app.arr, 1, 3333) //是响应式
app.arr.length = 3 //不是响应式
app.arr.splice(3) //是响应式
- 声明响应式: property 由于 Vue 不允许动态添加根级响应式 property,所以你必须在初始化实例前声明所有根级响应式 property,哪怕只是一个空值
- Vue 在更新 DOM 时是异步执行的,为了在数据变化之后等待 Vue 完成更新 DOM,可以在数据变化之后立即使用
Vue.nextTick(callback)
。这样回调函数将在 DOM 更新完成后被调用。Vue.nextTick(callback)
或实例方法app.$nextTick(callback)
$nextTick()
返回一个Promise
对象,所以你可以使用新的 ES2017async/await
语法完成
this.title = ''
this.$nextTick(() => {
alert(this.title)
})
this.title = 123
//alert(123)
参考文档
结语
如果对你有帮助的话,请点一个赞吧