![a41159adfdcc7aad179d9f9353d26208.png](https://i-blog.csdnimg.cn/blog_migrate/0b4fb001931a8de47f0e0df00e878d24.jpeg)
其实关于 Vue 的数据响应式的主要原理在官方文档中已经介绍了(戳这里),但是看了好几次还是一知半解,接下来来看看 Vue 是如何实现的吧。
什么是响应式
首先,我们得知道什么是响应式,其实这是一个比较模糊的概念,举一个简单的例子:如果我朝你打一拳过去,你肯定会躲吧,我叫你一声你会应我吧(放心,不会收到葫芦里~)。还有更实际的例子:响应式页面 ——— 一种页面布局方式,页面的布局会随着页面大小的改变而改变。
而 Vue 的数据响应式是什么意思呢?其实文档中也写到了:
而当你修改它们时,视图会进行更新。
如何知道一个数据是否变化呢?答案是:监视他!给他装一个「监听器」!
那怎么装呢?先来看一个简单的 Vue 示例:
const _data = {
n:1
}
console.log(_data) // {n:0}
const vm = new Vue({
data:_data,
template: `
<div>{{n}}</div>
`
}).$mount("#app")
console.log(_data) // {__ob__: Observer}
这里我们发现控制台中打印的 _data
的不一样了,因为 Vue 对传入的 data
进行了处理,点开这个对象,我们发现对象内部多了几个函数,其中就有 get 和 set,这就是 Vue 文档中说所的 getter/setter。
ES6 的 getter/setter
getter 和 setter 怎么用呢?我们还是直接上代码:
// 一个人
const people = {
姓:'张',
名:'三'
}
//如果我想得到姓名,可以直接在对象中添加一个函数
const people1 = {
姓:'张',
名:'三',
姓名(){
return this.姓 + this.名
}
}
console.log(people1.姓名()) // 张三
// 然后 ES6 的 getter 帮我们简化了一下
const people2 = {
姓:'张',
名:'三',
get 姓名(){
return this.姓 + this.名
}
}
console.log(people2.姓名) // 张三
// 只是简化了函数的括号,没什么大不了的
//然后我又需要对姓名进行设置,这时候就可以用到 setter
const people3 = {
姓:'张',
名:'三',
get 姓名(){
return this.姓 + this.名
},
set 姓名(name){
this.姓 = name[0]
this.名 = name.slice(1)
// 获取姓名的方式并不严谨,只是一个示例
}
}
people3.姓名 = '王五'
console.log(people3.姓名) // 王五
// 设置 set 以后,'= xxx' 这个操作便会调用 set 函数,等号后面的值便是函数的参数。
当我们将赋值和取值的过程转变为函数以后,我们就可以在函数中添加各种操作了,比如告诉视图需要改变、告诉computed
和 watch
该工作啦,这个不属于本文的内容,我们只要知道这样就可以监听数据的变化就好了。
但是,getter 和 setter 只能在对象初始化的时候设置,可是 Vue 中传入的对象是已经初始化完毕的对象,Vue 是怎么对其进行监听的呢?答案是:Object.defineProperty
Object.defineProperty
Object.defineProperty()
方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回此对象。
其中也包括 getter/setter,废话不多,上代码:
// 我们需要让一个对象全面被我们监控,这样才能及时更新视图
let data = {
n:0
}
// 使用 Object.defineProperty
let data1 = {}
data1._n = 0 // 先用一个属性存住 n 的值
Object.defineProperty(data1,'n',{
get(){
return this._n
},
set(xxx){
console.log('监听到了~')
this._n = xxx
}
})
data1.n = 5 // 监听到了~
//但是,这时如果我们直接操作 data1._n
data1._n = 6 // 什么都没输出
console.log(data1.n) // 6
//继续改进!既然你喜欢直接操作对象,那我用一个代理函数,传入一个匿名对象,输出一个我监听的代理对象,这样你就操作不了了吧
function proxy({data}){
const obj = {}
// 这里写死了'n',正常时需要遍历 data 的所有 key 进行操作的
Object.defineProperty(obj,'n',{
get(){
return data.n
},
set(xxx){
data.n = xxx
console.log('监听到了,值是'+data.n)
}
})
return obj
}
let data2 = proxy({data:{n:0}})
//再试试
data2.n = 5 // 监听到了,值是5
// 但是,好像还是有问题,如果我们像最上面的例子那样写
let _data = { n: 0 }
let data3 = proxy({data:_data})
data3.n = 7 // 监听到了,值是7
_data.n = 8 // 什么都没有,值却改变了
console.log(data3.n) // 8
// 既然这样,那我把传入的对象也一起监听上!
function proxy2({data}){
// 这里写死了'n',正常时需要遍历 data 的所有 key 进行操作的
let value = data.n
Object.defineProperty(data,'n',{
get(){
return value
},
set(xxx){
value = xxx
console.log('原始对象监听到了,值是'+data.n)
}
})
const obj = {}
Object.defineProperty(obj,'n',{
get(){
return data.n
},
set(xxx){
data.n = xxx
console.log('代理对象监听到了,值是'+data.n)
}
})
return obj
}
let _data = { n: 0 }
let data4 = proxy2({data:_data})
data4.n = 9 // 原始对象监听到了,值是9
// 代理对象监听到了,值是9
_data.n = 10 // 原始对象监听到了,值是10
都搞定咯,这时我们回头看一眼最上面的案例
const vm = new Vue({data:_data})
let data4 = proxy2({data:_data})
是不是有点像?目前我们就简单实现了 Vue 数据响应式。当然,真正的代码肯定没有这么简单,大家有兴趣可以自行研究源代码~
如果有错误的地方,欢迎大家指正!