Vue 数据响应式|Object.defineProperty 的问题

441 阅读4分钟

什么是响应式

  1. 我给你一哈,你会疼,那你就是响应式
  2. 若一个物体能对外界的刺激作出反应,它就是响应式

getter 和 setter

//这样写是为了引用完整版 Vue
import Vue from "vue/dist/vue.js"; 


Vue.config.productionTip = false;

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 也是可以的

我们平时一般是如何变的?

// 3秒后 0 => 10 当我点击的时候 +10 ,除了用 this.n 之外,还可以用 myData.n += 10
const vm = new Vue({
  data: myDate,
  template: `
    <div>
      {{n}}
      <button @click="add">+10</button>
    </div>
  `,
  methods: {
    add(){
      this.n+=10
      // myData.n += 10
    }
  },
}).$mount('#App')

优先使用 this.n , 因为很多时候我们不会去声明 myData

什么是getter 和 setter

get 就是在函数前面加一个 get , 然后调用它的时候不需要加 ()

set 必须要接收一个新的值,这个值叫什么无所谓

let obj0 = {
  姓: "高",
  名: "圆圆",
  age: 18
};

// 需求一,得到姓名

let obj1 = {
  姓: "高",
  名: "圆圆",
  姓名() {
    return this.姓 + this.名;
  },
  age: 18
};

console.log("需求一:" + obj1.姓名());
// 姓名后面的括号能删掉吗?不能,因为它是函数
// 怎么去掉括号?

// 需求二,姓名不要括号也能得出值

let obj2 = {
  姓: "高",
  名: "圆圆",
  get 姓名() {
    return this.姓 + this.名;
  },
  age: 18
};

console.log("需求二:" + obj2.姓名);

// 总结:getter 就是这样用的。不加括号的函数,仅此而已。

// 需求三:姓名可以被写

let obj3 = {
  姓: "高",
  名: "圆圆",
  get 姓名() {
    return this.姓 + this.名;
  },
  set 姓名(xxx){
    this.姓 = xxx[0]
    this.名 = xxx.slice(1)
  },
  age: 18,
};

obj3.姓名 = '刘诗诗'

console.log(`需求三:姓 ${obj3.姓},名 ${obj3.名}`)

// 总结:setter 就是这样用的。用 = xxx 触发 set 函数

// 	log的值
// 需求一:高圆圆 
// 需求二:高圆圆 
// 需求三:姓 刘,名 诗诗 

截屏2022-01-15 下午12.48.39.png 姓名: (...) 它的意思是我确实可以对 姓名 进行读和写,但是并不存在一个叫做 姓名 的属性,这个读写是通过下面那个get 姓名 set 姓名 来完成的

Object.defineProperty

// 假设我想在 obj3 身上在添加新的 get 和 set 该如何添加??? 
// 以下就是在定义完一个对象之后,你想在它(obj3)身上在额外的添加新的 get 和 set 的时候,
// 就只能通过 Object.defineProperty 来做了
// 一般来说你不要以为 "xxx" 定义的那些属性是存在的,"xxx" 定义的那些属性是不存在的,
// 我定义的这个 get set 属性是不存在的
// 我如果 get(){ return this.xxx} 会死循环
// 因为我在 return this.xxx 时,就会调用 get ,调用 get 的时候又会调用 this.xxx,如此反复,死循环

// 所以要先声明一个局部变量, 只要不要去使用 xxx 就好了
  var _xxx = 0  // 用来存放 xxx 的值的
  
  //  我们给 obj3 添加了一个虚拟属性 xxx ,它有一个 get 和一个 set 
  
  Object.defineProperty(obj3, 'xxx', {
  
    // 这里不用写成 get xxx(){}, 这里的 get(){} 就是 get xxx(){}
    get(){
      return _xxx
    },
    set(value)
      _xxx = value
    }
  })

代理和监听

需求一:用 Object.defineProperty 定义 n

给data1 添加一个叫做 n 的属性,这个属性的值为 0 ,注意它的值就是 0 ,它的值不是 value: 0
let data1 = {}

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

需求二:n 不能小于 0

let data2 = {}

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

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

console.log(`需求二:${data2.n}`)
data2.n = -1
console.log(`需求二:${data2.n} 设置为 -1 失败`)
data2.n = -1
console.log(`需求二:${data2.n} 设置为 1 成功`)

如果对方直接使用 data2._n 呢?

那就不暴露,使用代理

需求三:使用代理

如何做到不让别人访问我的 _n

let data3 = proxy({ data: {n: 0} })

function proxy({data}){ // const {data} = options
  const obj = {}

  Object.defineProperty(obj, 'n', {
    get(){
      return data._n
    },
    set(value){
      if(value<0)return
      data._n = value
    }  
  })
  return obj // obj 就是代理
}

{n: 0} 这个对象无法去做修改, 它没有名字

代理举例: 我对中介说啥,中介就对房东说啥,这个中介就是代理,中介会帮房东把关(n<0的时候 return)

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

先拿原始的值,然后删掉,这样你就没有办法去操控原始的数据了

然后我在创建一个虚拟的 n, delete data.n 可以不写,因为当我写这句话的时候,我声明的虚拟 n 和之前的 n

是重名的,它就会用新的直接覆盖之前的

Vue 的 data 是响应式

链接

  1. const vm = new Vue({data: {n: 0}})
  2. 我如果修改 vm.n,那么 UI 中的 n 就会响应我
  3. Vue2 通过 Object.defineProperty 来实现数据响应式
  • 所有的数据放到一个对象里,然后去操作对象,对象就会改变数据,来自动更新视图
  • 只需关注页面数据如何变化,因为数据变化后,视图也会自动更新
  • 数据改变后,使用该数据的地方被动发生响应,更新视图
  • Vue不能检测到对象属性的添加或删除,如果一开始没有在Data上声明属性,就算你对这个属性做出更改,也不会更新UI。解决的方法是手动调用Vue.set或者this.$set 截屏2021-12-20 下午11.50.20.png

Vue 的 data 的 bug

Object.defineProperty 的问题

我们要用这个函数的时候需要给它一个对象
Object.defineProperty(obj, 'n', {...})
上面的问题就是 必须要有一个 'n',才能监听&代理 obj.n
如果没有给 n 怎么办?

  1. Vue会给出警告

截屏2021-12-20 下午10.26.29.png 2. 此时我点击 set b,视图中不会显示 1 截屏2021-12-20 下午10.34.18.png 因为 Vue 没办法监听一开始不存在的 obj.b

解决办法

  1. 把 key 都声明好,后面不再加属性

截屏2021-12-20 下午10.39.00.png

  1. 使用 Vue.set 或者 this.$set ,它们两是没有任何区别的加 $ 是为了防止和 data 里的重名

截屏2021-12-20 下午10.45.02.png

Vue.set 和 this.$set

Vue.set(this.obj, 'b', 1) this.$set(this.obj, 'b', 1)

作用

  • 新增 key
  • 自动创建代理和监听(如果没有创建过)
  • 触发 UI 更新(但不会立即更新)

举例

this.$set(this.object, 'm', 100) 这里的 this 指的是 vm

数组的变异方法

如果data里有数组咋办?

没有办法提前声明所有 key

数组的长度可以一直增加,下标就是 key 截屏2021-12-20 下午10.58.05.png

把数组理解为 [0: 'a'; 1: 'b'; 2: 'c'] ,我现在添加一个 d ,就相当于添加一个下标,和之前我们添加b,没有区别,所以 Vue 观察不到这个现象,因为你一开始告诉我这个数组的下标是 0 1 2,现在加一个 3 ,我无法知道

改成 this.$set(this.array, 3, 'd') 这样是可以成功添加,那是不是说明我只要操作数组我就要 set,因为数组不确定有多少没有办法提前声明

Vue 会篡改这个数组,它会在中间加一层原型,这个原型有7种方法,这个和以前的数组方法不一样,他们首先会调以前的数组方法,调完以后通知 Vue 你给我添加一个监听和代理

篡改数组 API ,见文档 👉🏻 变异方法

截屏2021-12-20 下午11.13.30.png

怎么篡改的

有点类似以下这种 截屏2021-12-20 下午11.28.48.png 截屏2021-12-20 下午11.25.14.png

总结

对象中新增的key

  1. Vue没有办法事先监听和代理
  2. 要使用set来新增key,创建监听和代理,更新UI
  3. 最好提前把属性都写出来,不要新增key
  4. 但数组做不到不新增key

数组中新增的key

  1. 也可用set来新增key,更新UI
  2. 不过尤雨溪篡改了7个API方便我们对数组进行增删
  3. 这7个API会自动处理监听和代理,并更新UI

结论:数组新增key最好通过7个API

注意

  1. this.$set 作用于数组时,并不会自动添加监听和代理。
  2. 使用 Vue 提供的数组变异 API 时,会自动添加监听和代理 set 了之后再用 this.array[n] += 1 是否会触发 UI 更新(答案是不会)