Vue数据响应式

54 阅读1分钟

该文是深入理解构造选项里的data

const myData={
  n:0
}
console.log(myData)  //{n:0}
new Vue({
  data:myData,
  template:`
    <div>{{n}}</div>
  `
}).$mount("#app")
setTimeout(()=>{
  myData.n+=10
  console.log(myData)  //{n:...}
},3000)

两次console.log(myData)的结果不一样,保留疑问,先学习getter、setter和Object.defineProperty

一、getter、setter

1、getter

let obj1={
  姓:"高",
  名:"圆圆",
  姓名(){
    return this.姓+this.名
  }
}
console.log(obj1.姓名())  //高圆圆
let obj2={
  姓:"高",
  名:"圆圆",
  get 姓名(){
    return this.姓+this.名
  }
}
console.log(obj2.姓名)  //高圆圆
  • 两段代码差异之处为,有无get和打印姓名是有无()
  • 这个用法就叫getter,可以获取一个值(不加()的函数,obj2.姓名也叫计算属性)

2、setter

let obj3={
  姓:"高",
  名:"圆圆",
  get 姓名(){
    return this.姓+this.名
  },
  set 姓名(xxx){
    this.姓:xxx[0]
    this.名:xxx.substring(1)
  }
}
obj3.姓名="高媛媛"
console.log(obj3.姓名)  //高媛媛
  • 这个用法就叫setter,当obj3.姓名时触发set函数
console.log(obj3)  //得到如下内容
//姓:高
//名:媛媛
//姓名:(...)
//get 姓名:
//set 姓名:
//...
  • 其中,姓名:(...)和上述还存疑的n:...一致
  • 姓名:(...)不是一个真实属性,是可以对其读和写,通过getter和setter
  • 所以,也并不存在n:...这个属性,是有get n和set n模拟对n的读和写

二、Object.defineProperty

  • 在定义完一个对象后,如果想在其额外添加新的get和set,或添加属性value时使用
//接上obj3
var _xxx=0
Object.defineProperty(obj3,'xxx',{
 get(){
   return _xxx
 },
 set(value){
  _xxx=value
 }
})
  • 定义的'xxx'属性不存在,如果return this.xxx会死循环,所以用_xxx去承载

1、拓展1:通过getter和setter进行条件判断

//需求:n不能小于0,即data2.n=-1无效,data2.n=1有效
let data2={}
data2._n=0
Object.defineProperty(data2,'n',{
  get(){
    return this._n
  },
  set(value){
    if(value<0) return
    this._n=value
  }
})
console.log(data2.n)  //0
data2.n=-1
console.log(data2.n)  //0(-1无效)
data2.n=1
console.log(data2.n)  //1(1有效)
  • 思考:如果直接篡改data2._n=-1,console.log(data.n) //-1,需求无法实现

2、使用代理

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就是代理(设计模式)
}
console.log(data3.n)  //0
data3.n=-1
console.log(data3.n)  //0(-1无效)
data3.n=1
console.log(data3.n)  //1(1有效)
  • 思考:如果用一个中间值let myData={n:0};let data4=proxy({data:myData}),myData.n=-1,console.log(data4.n)//-1,需求无法实现

3、监听data

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
    }
  })  //以上就是加了监听
  const obj={}
  Object.defineProperty(obj,'n',{
    get(){
      return data.n
    },
    set(value){
      if(value<0)return
      data.n=value
    }
  })
  return obj
}
console.log(data5.n)  //0
myData5.n=-1
console.log(data5.n)  //0(-1无效)
myData5.n=1
console.log(data5.n)  //1(1有效)

三、理解new Vue

综上代码有:let data5=proxy2({data:myData5}),和let vm=new Vue({data:myData})相似,由此去理解new Vue做了哪些事

  1. 会让vm成为myData的代理(proxy)
  2. 会对myData的所有属性进行监控 微信图片_20230726104133.png(截图来自饥人谷课件)
  • 防止myData的属性变了而vm不知
  • 知道后可调用render(data)更新UI
  • getter和setter用于对属性的读写进行监控

四、数据响应式

1、Vue的data是响应式

  • const vm=new Vue({data:{n:0}})
  • 若修改vm.n,那么UI中的n就会响应
  • Vue通过Object.defineProperty来实现

2、Vue的data有bug

  • Object.defineProperty(obj,'n',{...})
  • 必须要有'n',才能监听和代理obj.n
  • 那如果忘记给n怎么办?
new Vue({
  data:{},
  template:`
    <div>{{n}}</div>  //会给出一个警告,然后什么都不显示
  `
}).$mount("#app")  
new Vue({
  data:{
    obj:{  //Vue只检查第一层属性,发现有就不警告了
      a:0
    }
  },
  template:`
    <div>
      {{obj.b}}  //但是Vue无法监听一开始不存在的obj.b
      <button @click="setB">set b</button>
    </div>
  `,
  methods:{
    setB(){
      this.obj.b=1  //无法执行,无法显示
    }
  }
}).$mount("#app")
  • 如何解决?使用Vue.set/ this.$set 新增key
  • Vue.set(this.obj,'b',1)
  • this.$set(this.obj,'b',1)
  1. 会自动创建代理和监听
  2. 触发UI更新(但不会立即更新)

3、数组的变异方法

new Vue({
  data:{
    array:["a","b","c"]  //没法提前声明所有key,长度没法预测
  },
  template:`
    <div>
      {{array}}
      <button @click="setD">set d</button>
    </div>
  `,
  methods:{
    setD(){
      this.array[3]="d"
      Vue.set(this.array,3,'d')  
    }
  }
}).$mount("#app")
  • 因为数组长度没法预测,我如何得知是下标为3的要更新
  • 所以可将第13、14行代码改写为用push,this.array.push('d')
  • 注意:该push不是以前的数组push,而是被尤雨溪修改过的push
  • 该push,保留以前push的功能;帮你set,更新UI
  • 包括以前的pop、push、reverse、shift、sort、splice、unshift都被修改了
  • 修改代码逻辑:
  1. 先继承数组,记住原来的length
  2. 当push时,将读出新的length,遍历每个key
  3. 将每个新增的key都告诉Vue,帮你set
  • 对于数组中新增的key
  1. 用set新增key,会更新UI,但不会创建监听和代理
  2. 这七个API会更新UI,但不会自动处理监听和代理
  3. 只用this.array[n]=xxx,不会更新UI,也不会自动处理监听和代理
  4. 结论:对数组key的操作最好使用这七个API