我对Vue数据响应式的理解

344 阅读1分钟

前言:我还是刚入门的前端,理解可能有失偏颇,这篇文章只是简单的个人总结,方便自己用。

使用代理和Object.defineProperty

代理是一种设计模式:对一个对象的属性读写,全权由另一个对象负责,这起到一种保护数据不会被暴露给用户的作用。通过Object.defineProperty方法,我们可以给对象添加属性,还可以添加getter/setter用于对属性的读写进行监控。

function proxy({data}){
  const obj = {}
  Object.defineProperty(obj, 'n', {
    get(){ return data.n },
    set(value){
      if(value < 0) return; 
      data.n = value
    }
  })
  return obj;
}

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

console.log(`初始值: ${data.n}`)  // 初始值: 0
data.n = -1
console.log(`设置为-1,属性n的值为:${data.n}, 设置失败`)  // 设置为-1,属性n的值为:0, 设置失败
data.n = 1
console.log(`设置为1,属性n的值为:${data.n}, 设置成功`)  //设置为1,属性n的值为:1, 设置成功

变量data指向proxy函数(proxy函数传入一个匿名对象作为实参)返回的一个代理对象obj,我们通过代理对象读写真实对象的属性,而不能直接读写真实对象。

加入了监听的代理

通过使用代理避免了对目标对象进行直接读写,但通过使用特殊方法,仍然能够绕过代理进行直接读写。

let myData = {n: 0}
let data2 = proxy({ data: myData})
console.log(`${data2.n}, 原始数据为0`)
myData.n = -3
console.log(`${data2.n}, 设置为-3,成功了!`)

为了防止代理被绕过,我们需要在代理方法里添加一个监听。思路就是监听器获取目标对象的原始值,在改写这个目标对象的属性值时,使用setter并设置一定的规则,覆写(即删除并修改)原来的属性。代码如下:

function proxy2({data}){
  let value = data.n
  Object.defineProperty(data, 'n', {
    get(){ return value },
    set(newValue){
      if(newValue < 0) {
        console.log("已拒绝你的修改请求:只接受正数")
        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;
}

let myData = {n: 0}
let data3 = proxy2({data: myData})
console.log(`初始值: ${data3.n}`)
myData.n = -1
console.log(`设置为-1,属性n的值为:${data3.n}, 设置失败`)
myData.n = 1
console.log(`设置为1,属性n的值为:${data3.n}, 设置成功`)

Vue2数据响应式

vm = new Vue({data: myData})

上面的实验体现出来的思想即Vue2数据响应式的基础原理(之一)。当我们实例化一个Vue对象时,以上面代码为例,vm就成为了myData的代理(proxy),vm就会对myData的所有属性进行监控,一旦获悉数据发生变化,就可以自动调用render方法进行页面渲染,这就体现了响应(responsive)特性。

Vue2的一个小bug

Vue只会监听&代理事先声明过的数据,如果在后续添加新数据,可能就无法实现数据响应式。解决办法就是使用Vue.set或者this.$set方法。该方法的作用是:

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

数组的变异方法

当传入的数据是一个不定长数组时,我们很难预先设置好所有key,所以Vue提供了7个变异的数组方法(英文定义为'mutator'),其实就是继承了Array对象,然后对一些方法进行覆写,分别是:

  • push
  • pop
  • shift
  • unshift
  • splice
  • sort
  • reverse 当使用这些数组变异的API时,会自动添加监听和代理。
    需要注意的是:this.$set作用于数组时,并不会自动添加监听和代理, 也就无法实现响应,原因未知。所以新增key最好使用变异方法API。