Vue数据响应式

111 阅读3分钟

前置知识:getter和setter

  • 例一
let obj={
    姓:'张',
    名:'三',
    姓名(){return this.姓 +this.名}
}
console.log('需求一:'+obj.姓名())      //调用姓名属性需要加括号执行这个函数
//结果:需求一:张三
  • 例二 getter
let obj={
    姓:'张',
    名:'三',
    get 姓名(){return this.姓 +this.名}
}
console.log('需求二:'+obj.姓名)      //调用时不用加上括号,也能执行
结果  需求二:张三
  • 例三 setter
let obj = {
    姓: '张',
    名: '三',
    set 姓名(xxx) {
       this.姓 = xxx[0],
       this.名 = xxx.substring(1)
    }
}
obj.姓名 = '李四'    //接收新的参数,修改原有的属性
console.log(`需求三: 姓:${obj.姓} 名:${obj.名}`)
// 结果 需求三: 姓:李 名:四

Object.defineProperty实现若干需求

基本原理

  • 场景:obj对象已经声明,在对象中添加虚拟属性
let obj = {
    姓: '张',
    名: '三'
}
var _xxx=0    // 由于xxx属性不存在,需要引入一个变量_xxx用于存放传入的值
Object.defineProperty(obj, 'xxx', {       //obj对象里面添加一个虚拟属性xxx,里面包含了get和set
    get() {
        return _xxx
    },
    set(value) {
         _xxx=value
     }
})

需求一

空对象设置属性值

   let data2={}  

Object.defineProperty(data2,'n',{value:0})   //空对象里面新增一个新的虚拟属性n,值为0

console.log(`需求一:${data2.n}`)     //结果为 0

需求二

data2.n的值不能小于0,data2.n=1有效,data2.n=-1无效

let data2 = {};
data2._n = 0;                          //——n用于存放n的值
Object.defineProperty(data2, "n", {
  get() {                                 //自动调用虚拟属性n
    return this._n;
  },
  set(value) {
    if (value < 0) {
      console.log("传入的值不能小于0");  
    } else {
      this._n = value;        
    }
  }
});

data2.n = -1; 
console.log(`${data2.n}`);    //错误,提示传入的值错误,值依然是0

data2.n = 0; 
console.log(`${data2.n}`);   // 结果为0

data2.n = 1; 
console.log(`${data2.n}`);   //结果为1

需求三 使用代理

let data3 = proxy({ data: { n: 0 } });      //data3为proxy函数,接收一个对象
function proxy({ data }) {                  //解构赋值    {data}= { data: { n: 0 } } 
                                               data为{n:0}          
  const obj = {};                           //使用代理
  Object.defineProperty(obj, "n", {
    get() {                             
      return data.n;                     
    },
    set(value) {
      if (value < 0) return;
      data.n = value;
    }
  });
  return obj;              
}
data3.n=XX  //修改的是obj的n
//使用对象obj作为代理,不能直接修改对象{data:{n:0}}

  • 此方法的漏洞:
let myData={n:0}
let data=proxy({data:mydata})
//可以直接修改 myData.n来修改对象

需求四:拦截数据的篡改

let myData={n:0}                 //引入媒介,试图间接篡改对象
let data5=proxy({data:myData})
function proxy({data}){
  let value=data.n   //存n的初始值
  delete n              //此条代码可省略,下一行设置n虚拟属性,同名会删除n
                       //本可以通过myData对参数对象进行修改 ,但是当传入proxy函数中时,被修改的n直接被删除了
 Object.defineProperty(data,'n',{
   get(){
     return value
   },
   set(newValue){
     if(newValue<0)return
     value=newValue
   }
 })
 
 //以下代码为obj代理相同代码,略
 //代码
 略
 略
 略
 .
 //代码
}
data5.n=xxxx  //此时的n不是myData的n,而是虚拟属性n

小结:以上代码let data5=proxy({data:myData})与 Vue框架中const vm=new Vue({data:myData})的思想类似,进而引出Vue实例对于数据的处理

Vue处理数据的机制

  • Vue会让vm成为myData的代理
  • vm会对myData的所有属性进行监控
  • 当myData的属性发生变化,vm就会调用render(data),更新Ul
  • 同理,Vue对methods,computed也会进行类似的处理

1652454612.png

小结

什么是数据响应式

  • 如果修改数据(如修改vm.n或者直接修改data.n),vue实例会监听这个变化,Ul中涉及数据的部分就会响应这种变化(通过Object.defineProperty())

特殊情况

情况一:data 中的对象key声明问题

问题一:data中未放入数据

  • data中没有放入数据
  • 视图标签中引用了n
  • Vue警告,n未定义,在调用render的时候引用了n
//以下为Vue实例中的内容
   data:{ }

template:`<div>{{n}}<div>`

问题二(消除Vue的警告)

  • data中放入obj对象obj,包含a属性
  • 此时不会有警告,因为只会监听data{}这一层
  • 视图标签中引用obj.b
  • 由于Vue只在data{}中设置值的时候监听,后续声明obj.b的时候无法监听,导致obj.b不会出现在视图中
//以下为Vue实例中的内容
   data:{
   obj:{a:0}
   },

template:`<div>{{obj.b}}
<button @click='setB'>setb</button>
          <div>`methods:{
setB(){
        this.obj.b=1
        }
      }  

解决方法

  • 方法一:在放入data时就声明好属性data:{obj:{a:0,b:1})
  • 方法二:## Vue.set() / this.$set()
    • 作用:新增key
    • 自动创建代理和监听(如果没有创建过)
    • 触发UI更新(但不会立刻更新即异步更新)
//以下为Vue实例中的内容
   data:{
   obj:{a:0}
   },

template:`<div>{{obj.b}}
<button @click='setB'>setb</button>
          <div>`methods:{
setB(){
       Vue.set(this.obj,'b',1)   //新增key b ,创建代理和监听
       this.$set(this.obj,'b',1)  //与上条方法同理
        }
      }  

情况二:data中有数组-关于数组变异

  • 数组中添加d,失败

  • 使用set方法存在局限性,因为数组的长度是无法预测的

    • 与传入对象同理,新的key:4 未提前声明,vue无法监听
new Vue({
    data: {
        array:['a','b','c']
    },
    template: `
    <div>
    {{array}}
    <button @click='setD'>set D</button>
    </div>
    `,
    methods: {
        setD() {
            this.array[3]='d'
        }
    }
}).$mount('#app')
  • 使用Vue的push方法,与array.push不同
    • Vue的push方法对每个新增的key进行监听
```js
new Vue({
    data: {
        array:['a','b','c']
    },
    template: `
    <div>
    {{array}}
    <button @click='setD'>set D</button>
    </div>
    `,
    methods: {
        setD() {
            this.array.push("d")  //这里的push与原生js中的array.push不同
        }
    }
}).$mount('#app')
  • Vue中的push大致原理
//说明:以下代码不代表Vue源码,仅作为原理的大致解释
class VueArray extends Array {
    class VueArray extends Array { //新增加一层原型VueArray,继承以前的Array原型
        push(...args) { //新原型的push方法
            const oldLength = this.length // this就是当前数组
            super.push(...args) //会调用上一层原型(原来数组)的push方法
            console.log(' push ')
            for (let i = oldLength; i < this.length; i++) { //把之前的下标和现在新的下标找出来,中间的不就是新增加的,那就set他们就行了。
                Vue.set(this, i, this[i])
                    // key Vue
            }
        }
    }


小结
  1. Vue实例中,数据(data:{})中的对象新增属性(key),Vue无法直接进行监听和代理。应提前将所需的属性(key)写入到data的对象中,或者通过Vue.set/this.$set方法创建监听和代理
  2. 如果data中的对象是数组,在后续的操作中新增key,也可用Vue.set方法,但是数组的长度不好把控,存在局限性。因此优先使用Vue提供的7个API对数组进行操作](url),这7个方法会自动处理监听和代理更新UI