Vue 的核心在于:数据更新会引起视图更新。前提就在于,Vue 需要知道数据何时发生了变化。它是如何监测到的?
1. 原理
通过 Object.defineProperty
实现对数据变化的监测
这么说总有些晦涩难懂,来个小故事:
这天,你在宿舍玩游戏,你喊了一下你舍友,让他要下去吃饭的时候叫上你一起。饭点一到,你的舍友就来叫你,哥们吃饭去!
上面的故事,你给你的舍友设置了一个"属性",让他吃饭时记得来叫你。这时候你哪怕玩游戏正入迷着,也能监测到舍友要去吃饭的这个信息。
同理,Object.defineProperty
能添加一个可被监听的属性,一旦这个属性发生变化,Vue 就能知道并且做后续操作。
2. 探索
首先给出结论:在 Vue 中,能看到分配了 get
和 set
方法的变量,都是可以被 Vue 所监测到变化的变量。带着结论,我们往下探索。
在任意页面中,声明一个 age
变量,并在 mounted 中输出 this
// xxx.vue
mounted() {
console.log(this)
},
data () {
return {
age: 18
}
}
1)this
是什么?
可以看到,this 输出了一个组件实例对象
(也就是你的 xxx.vue 文件)
2)age 是否有对应的 get 和 set?
展开来查看组件实例对象中的 _data
,可以看到其中包含了 age
变量,及其 get
和 set
方法,这便说明了该变量可被 Vue 监测(修改该变量时,视图也会同步发生改变)。
在数据代理那篇文章,讲解过
_data
的相关内容,还未读过的小伙伴务必复习一下:【Vue | 补洞 | 09】数据代理
3)age 后面为什么是 (...)
?
我们都知道需要鼠标点击省略号,才能显示出 age 中的内容;点击的这个操作,就相当于读取了 age,触发了 get 方法
4)什么时候触发 set
?
当我们通过如 this.age = xxx
来对 age 赋值时,便会触发 set 方法
5)触发 get 和 set 后,Vue 会做什么?
- 触发 get 方法:Vue 就可以读取最新的值,并显示在视图上
- 触发 set 方法:在修改数据后,Vue 就会去重新渲染视图,将最新的数据显示在界面上
6)我只定义了 age,但是看到的 _data
却包含了 get 和 set?
Vue 在底层将 age 做了转换,将变量转变为 可监测的数据对象 Observer
,简单过程理解为:const newAge = new Observer(age)
,newAge
就能被监测到变化了。
3. 简单实现
我们来实现一个将 age 转换为 newAge 的函数,实现数据变化监测。
1)Object.defineProperty(obj, prop, options)
用法
- obj:需要被监测变化的 对象
- prop:需要被监测变化的 属性
- options:可以配置 get 和 set 方法
let obj = {}
Object.defineProperty(obj, 'name', {
get() {
console.log(`触发get方法,读取值为${obj[key]}`)
return obj[key]
},
set(val) {
console.log(`${key}被改变了`)
obj[key] = val
}
})
2)Observer
函数的简单实现
let data = {
name: '波吉',
address: '福州',
}
const obs = new Observer(data)
let vm = {}
vm._data = data = obs
// 用一个构造函数将对象转换为可监测的对象
function Observer(obj) {
let keys = Object.keys(obj)
keys.forEach((key) => {
Object.defineProperty(this, key, {
get() {
console.log(`触发get方法,读取值为${obj[key]}`)
return obj[key]
},
set(val) {
console.log(`${key}被改变了`)
obj[key] = val
},
})
})
}
3)效果
① 初始值为
② 读取属性时
③ 修改属性的值时
4. 备注
- 在对象中,不管层叠多少层,或者数组中包含的对象属性,只要属性修改都可以被 Vue 监测到
- 修改了属性后,一旦触发了 set 方法,Vue 会重新解析模板、虚拟DOM对比等一系列操作,再重新渲染到界面上
- 注意!
Object.defineProperty
无法监测到属性的新增
和删除
,示例如下
-
为解决直接通过
data.xxx
无法添加响应式的情况,Vue 提供了一个 API 可以用于给对象新增响应式属性,用法this.$set(this.data, 'age', 19)
5. 总结
- Vue2 通过
Object.defineProperty
实现响应式数据监听 - 响应式数据的共同特点:有
get
和set
方法 Object.defineProperty
的设计缺陷:- 无法监测属性的新增和删除
- 无法监测数组的变化(后续文章会讲解到 如何监测数组变化)