2021-07-24

什么叫响应式?Vue的数据响应式如何实现?

  • 一个物体对外界的刺激作出的反应,就叫响应式。Vue2主要是通过Object.defineProperty函数来实现数据响应式。

首先来说一下ES6的getter(get)和setter(set)属性,如下例子:

let obj1 = {
  姓: "周",
  名: "杰伦",
  姓名() {
    return this.姓 + this.名;
  },
  age: 18
};
console.log("需求一:" + obj1.姓名());
//log出:需求一:周杰伦

// 需求二,调用对象的姓名时不用括号也能得出值
let obj2 = {
  姓: "周",
  名: "杰伦",
  get 姓名() {
    return this.姓 + this.名;
  },
  age: 18
};
console.log("需求二:" + obj2.姓名);
//log出:需求二:周杰伦
//总结:通过getter(get)就可以调用不加括号的函数,仅此而已。

// 需求三:姓名可以被对象外部赋值
let obj3 = {
  姓: "周",
  名: "杰伦",
  get 姓名() {
    return this.姓 + this.名;
  },
  set 姓名(xxx){
    this.姓 = xxx[0]
    this.名 = xxx.slice(1)
  },
  age: 18
};
obj3.姓名 = '周杰伦'
console.log(`需求三:姓 ${obj3.姓},名 ${obj3.名}`)
//log出:需求三:姓周,名杰伦
// 总结:通过setter(set),并传入xxx参数触发set函数

Object.defineProperty

  • 语法:Object.defineProperty(obj, prop, descriptor),obj要定义属性的对象,prop要定义或修改的属性的名称或Symbol.
//当我们声明完一个对象时,如何增加属性
let data2 = {}
data2._n = 0 // _n 用来存储 n 的值
Object.defineProperty(data2, 'n', {
  get(){
    return this._n
  },
  set(value){
    if(value < 0) return
    this._n = value
  }
})
data2.n = -1//设置失败,原因是传入的对象不符合条件,什么也不返回
console.log(`${data2.n} `)//log出来的结果还是get返回的值0
data2.n = 1
console.log(`${data2.n}`)//满足需求,结果是通过set传入的
//总结:上述方法会存在一个问题,就是如果直接将data._n赋值为1的话,就能设置成功,那么们就要用到监听和代理
  • 如何利用Object.defineProperty进行监听和代理
let myData5 = {n:0}
let data5 = proxy2({ data:myData5 }) // 括号里是匿名对象,无法访问
function proxy2(options){
  const {data}=options
  let value = data.n//存下n的值
  Object.defineProperty(data, 'n', {
    get(){
      return value
    },
    set(newValue){
      if(newValue<0)return
      value = newValue//将新的并符合条件的newValue值先赋值给value,接下来通过代理的方法将value值赋给data.n
    }
  })
  // 上面的这一段代码就是监听data
  const obj = {}//创建一个空对象
  Object.defineProperty(obj, 'n', {
    get(){
      return data.n
    },
    set(value){
      if(value<0)return//这句话多余了
      data.n = value
    }
  })
  return obj // obj 就是代理,返回的obj就是data5
}
myData5.n = -1//就算直接在myData5上赋值,也会被data5监听到
console.log(`需求五:${data5.n},设置为 -1 失败了`)
myData5.n = 1
console.log(`需求五:${data5.n},设置为 1 成功了`)
  • 我们不妨比较一下下面两句代码
let data5 = proxy2({ data:myData5 })
let vm = new Vue({data:myData})

根据上面两行代码的对比,我们得出:Object.defineProperty的监听和代理原理就是Vue源代码所体现出来的原理,实际上就是让vm成为myData的代理(proxy),会对myData所有的属性进行监控,无论你对myData的属性进行读写,都会被Vue的set和get读取到。当vm知道了myData改变后,就会调用render(data),这样UI页面就得以重新渲染UI=render(data),同理修改了vm.n,UI中的n也会重新渲染,这就是Vue通过Object.defineProperty来实现数据响应式的基本过程

  • 但是Object.defineProperty有个问题,比如:Object.defineProperty(obj,‘n’,{…}),必须要有一个’n’,才能监听和代理obj.n,Vue就用了vue.set和this.$set解决了这个问题,当你忘记写某数据时,他会自动帮你添加这个key,并创建监听和代理,并触发UI更新。
new Vue({
  data: {
    obj: {
      a: 0 // obj.a 会被 Vue 监听 & 代理
    }
  },
  template: `
    <div>
      {{obj.b}}//由于data上没有b,他是不会被显示的
      <button @click="setB">set b</button>
    </div>
  `,
  methods: {
    setB() {
      //this.obj.b = 1; 不会显示1
      Vue.set(this.obj,'b',1)//Vue会自动帮你加b数据,并且进行监听和代理
      this.$set(this.obj,'b',1)//两种方式都可以
    }
  }
}).$mount("#app");
  • 但是当我们的data是一个数组时,我们不能确定数组的长度,所以用Vue.set和this.$set不切实际。此时我们应该利用Vue篡改数组的7个API解决,分别push()pop()shift()unshift()splice()sort()reverse(),例子如下
new Vue({
  data: {
    array: ["a", "b", "c"]
  },
  template: `
    <div>
      {{array}}
      <button @click="setD">set d</button>
    </div>
  `,
  methods: {
    setD() {
      this.array.push('d')//注意此时的push和Array的push并不是同一个push
    }
  }
}).$mount("#app");

我们可以用ES6来简单描述一下篡改的大致原理,但不一定是Vue源代码所显示的那样

class VueArray extends Array{//继承JS的数组类型
   push(...args){//
     const oldLength = this.length // 记下最开始数组的长度
     super.push(...args)//super表示去调用父类的push
     console.log(' push ')
     for(let i = oldLength; i<this.length; i++){//遍历增加后的数组
       Vue.set(this, i, this[i])//利用Vue.set的去告诉Vue,惊醒代理和监听
     }
  }
}

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值