《Vue-数据响应式-2》

166 阅读5分钟

前一篇博客中,我们知道了getter,setter以及Object.defineProperty的用法。

现在我们看一看当我们写下:

new Vue({
    data:{
        n:0
    }
})

Vue究竟对传进去的data做了什么

一. 例子

const myData = {
  n: 0
};
console.log(myData); // 本节课精髓

new Vue({
  data: myData,
  template: `
   <div>{{n}}</div>
  `
}).$mount("#app");

setTimeout(() => {
  myData.n += 10;
  console.log(myData); // 本节课精髓
}, 3000);

声明一个myData对象,然后打印出来(先不执行new Vue),看到myData是这样的:

就是一个正常普通的对象。

然后,new Vue创建一个实例,把myData的地址传给data。三秒之后把myData的n+10,再次打印此时的myData。

变成了一个很奇怪的东西:(...) 这里我们已经知道,把一个对象的某个属性设成getter,setter,那个属性就会变成这样。意思是n是不存在的,它是get n和set n

那么,一个对象经过了 new Vue被传给data选项之后,为什么变了呢?

二.

声明一个对象;

let data0 = {
  n: 0
}

需求一:用 Object.defineProperty 定义 n

let data1={}
Object.defineProperty(data1,'n',{
    value:0
})

用value指定值,意思是data1.n=0

可是这样写不是把事情搞复杂了吗?我本来可以直接定义n的

需求二:n 不能小于 0

即如果我让data.n赋值为-1,就是无效的,n还是原来的值。但是赋值为正数是有效的。

let data2 = {}

data2._n = 0 // _n 用来偷偷存储 n 的值

Object.defineProperty(data2, 'n', {
  get(){
    return this._n
  },
  set(value){
    if(value < 0) return
    this._n = value
  }
})

让_n偷偷存储n的值,给属性n设get,set。如果读data2的n,就会返回data2的_n;如果设置n的值,先判断如果传的值小于0,就直接return,不设置。否则就把value赋给data2的_n属性。

即:读n,就是读_n;写n,就是写_n的值。在set里判断条件。

console.log(data2.n) 0

data2.n=-1
console.log(data2.n)   //0
data2.n=1
console.log(data2.n)   //1

这样就做到了让n的值不能小于0.

可是有杠精问:你设了data2._n,那我直接data2._n=-1 不就修改了吗?

需求三:使用代理

既然你说你能直接访问名字修改,那我不给你名字,你不就访问不到了。

let data3 = proxy({ data:{n:0} }) // 括号里是匿名对象,无法访问

function proxy({data}/* 解构赋值 */){
  const obj = {}
  Object.defineProperty(obj, 'n', { 
    get(){
      return data.n
    },
    set(value){
      if(value<0)return
      data.n = value
    }
  })
  return obj // obj 就是代理
}

我把有data属性的对象作为参数,传给了proxy函数。这是个匿名对象,没名字你就访问不到。

proxy的形参,意思是接受一个options,把options.data命名为data(解构赋值)。

在proxy函数里,给对象obj设置有get和set的属性n。实现:obj.n === data.n obj.n=1 === data.n=1,然后返回这个对象obj。

调用proxy时,又赋给了data3。也就是说,data3就是obj。读data3的n,就是读data的n;设置data3的n,就是设置data的n。

所以,obj就是代理。对data3的所有操作,都会由obj原封不动的传达给data。

console.log(data3.n) 0

data3.n = -1
console.log(data3.n)   //0
data3.n = 1
console.log(data3.n)   //1

现在我用了匿名对象,你改不了了吧?

但是!杠精又说:我先声明一个对象,把这个对象的地址传给data。让这个对象有了名字,我改myData:

let myData = {n:0}
let data4 = proxy({ data:myData })

console.log(data4.n)  //0
myData.n = -1
console.log(data4.n)   //-1   什么?修改成功了???

需求五: 就算用户擅自修改 myData,也要拦截他

let myData5 = {n:0}
let data5 = proxy2({ data:myData5 }) 

function proxy2({data}){  /* 解构赋值 */
  let value = data.n
  Object.defineProperty(data, 'n', {
    get(){
      return value
    },
    set(newValue){
      if(newValue<0)return
      value = newValue
    }
  })
  // 就加了上面几句,这几句话会监听 data

  const obj = {}
  Object.defineProperty(obj, 'n', {
    get(){
      return data.n
    },
    set(value){
      if(value<0)return//这句话多余了
      data.n = value
    }
  })
  
  return obj // obj 就是代理
}

新加的一段代码做了什么?

  1. 先把传进来的data的n放到value身上,先保存
  2. 把data的n属性设get,set。由于这个n和之后传进来的myData5的n同名,所以会覆盖。也就是说,myData5的原来的n已经消失了,现在的n是被设了get,set的n。

后一段代码还是使用obj代理,目的是对data5的读写会由obj原封不动传给data,也就是myData5。

console.log(data5.n)   //0

像四一样直接修改myData5的n:

myData5.n = -1
console.log(data5.n)    //0   失败了!!!
myData5.n = 1
console.log(data5.n)  //1

简单来说,先把传进来的data.n换成一个全新的n,这个全新的n由于设成了get,set,就有了监听功能。即使你通过myData5.n=-1 修改,你改的也不是原来的n了,而是具有监听功能的n。set会判断,如果小于0 ,也不给设置。

所以,即使myData5有名字,也不能随意修改了。

三. new Vue

let data5 = proxy2({ data:myData5 }) 
let vm = new Vue({data: myData})

这两句话是一个原理。所以第二句话做了这些事情:

  1. 让vm成为myData的代理。使用的this就是vm,vm就是myData
  2. 对myData的所有属性进行监控。myData身上的n也好,m也好,都会把他们一一挂到自己的value上保存一下。然后把他们换成有getter,setter的n/m。

监控的作用?对myData属性的所有改动,vm这个代理都要知道。 知道了之后,就可以render(data),渲染到页面。所以我们改this.n,都会更新到视图里。

所以一开始的问题就清楚了,一个data对象经过了new Vue之后,它里边的属性们就变成了有getter,setter的同名属性(虚拟),所以打印出来会显示(...)

Vue从来没有删掉data这个对象。是在这个对象身上做改动