Vue的数据响应式(双向绑定)

435 阅读3分钟

今天讲一讲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}

这两个输出的值都很奇怪,第一个还是个对象,但是打开对象一看却出现了这个情况:

image.png
我发现我解释不通这个现象,为什么我传入的值会变?于是我开始查找资料,最后我得出了结论,是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能够监听到对象发生了变化从而对整个页面进行了重新的渲染,这样就做到了响应式。