今天讲一讲vue的双向数据绑定。
数据的变化
在自学vue的过程中,我惊奇的发现,传给vue的数据居然会发生变化!我当时的代码如下:
const amout = {n:1}
console.log(amout) // {n:1}
new Vue({
data:amout,
template:`<div>{{n}}</div>`
}).$mount("#app");
console.log(amout); // {__ob__:Observer}
这两个输出的值都很奇怪,第一个还是个对象,但是打开对象一看却出现了这个情况:
我发现我解释不通这个现象,为什么我传入的值会变?于是我开始查找资料,最后我得出了结论,是vue在我传入这个值的时候,对我这个对象的做了改动,并将改动的值传给了我的对象的地址值。它是怎么做到的?为什么要这么做?首先解释为什么要这么做。
为什么?
答案很简单,因为vue需要监听数据的变化,从而时时的对页面的变动做出渲染,也就是达到vue数据响应式的功能。
怎么做到的?
要解释这个,首先得明确一个概念,什么是defineProperty,它是Object的一个方法,可以用它来给对象的某个值进行操作,比如我要给对象a的b属性赋值为c,代码如下:
const a = {b:"b"};
Object.defineProperty(a,"b",{
value:"c"
})
这样包裹对象后,再次输入对象a,就会得到我的第一个值,也就是n:(...),那么很明显,vue就是这么包装了一下。 那么问题来了,这么包装了是为了什么呢?于是我们尝试对之前对一个a对象中的b进行getter,setter封装
const a._b = "b" //用一个_b来存储b的值
Object.defineProperty(a,"b",{
get(){
return a._b
},
set(value){
fn//告诉vue我被修改了
return a._b = value
}
})
此时调用a.b得到的就是存储在_b中的值,通过setter方法,我们成功对数据进行了监听。但是问题还存在,此时用户可以通过外部修改_b的值,这样等同于b被修改了,同样会导致b的值不对劲。怎么办?于是再次进行封装,引入代理的概念。
const a = proxy({data:{b:0}});
function proxy({...data}){
const obj = {};
Object.defineProperty(obj,"b",{
get(){
return data.b
},
set(value){
fn//告诉vue我被修改了
return data.b = value
}
})
return obj
}
简而言之,就是用一个函数把要传入的东西包起来,然后告诉我再对立面的值进行修改,此时外面无法对本身的值进行修改了。但是还有问题,如果我对整个proxy里面传的值进行修改,它还是能成功修改数值。此时就不得不对外面传进来的数值,再次进行监听,这个时候,就很完美了。
function proxy2({...data}){
let value = data.b
Object.defineProperty(data, 'b', {
get(){
return value
},
set(newValue){
fn//告诉vue我被修改了
value = newValue
}
})
const obj = {}
Object.defineProperty(obj, 'b', {
get(){
return data.n
},
set(value){
fn//告诉vue我被修改了
data.n = value
}
})
return obj
}
这个时候,就应该是完美重现了vue做的本质操作,也就是它能神奇的响应式监听数据的根本原因。
总结
当你把一个普通的 JavaScript 对象传入 Vue 实例作为 data 选项,Vue 将遍历此对象所有的 property,并使用 Object.defineProperty 把这些 property 全部转为 getter/setter,这样使得Vue能够对这些对象进行代理。Object.defineProperty,这些 getter/setter 对用户来说是不可见的,但是在内部因为Vue的代理,Vue能够监听到对象发生了变化从而对整个页面进行了重新的渲染,这样就做到了响应式。