Vue数据响应式原理

156 阅读1分钟

当你把一个普通的 JavaScript 对象传入 Vue 实例作为 data 选项,Vue 将遍历此对象所有的 property,并使用 Object.defineProperty 把这些 property 全部转为 getter/setter。
下面做一些实验来实现一些需求:
1.用 Object.defineProperty 定义 n

let data1 = {}

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

console.log(`需求一:${data1.n}`)

2.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 成功`)

发现通过getter和setter方法来获取和设置属性值,就能在过程中添加一些条件来限制对原始数据的设置,这就是监听data。但是这种方式存在问题,如果有人通过data2._n 修改属性值还是无法避免数据被修改的结果,因此我们不应该将数据暴露
通过代理可以做到

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

function proxy({data}/* 解构赋值*/){
  const obj = {}
  // 这里的 'n' 写死了,理论上应该遍历 data 的所有 key,这里做了简化  
  Object.defineProperty(obj, 'n', { 
    get(){
      return data.n
    },
    set(value){
      if(value<0)return
      data.n = value
    }
  })
  return obj // obj 就是代理
}

// data3 就是 obj
console.log(`需求三:${data3.n}`)
data3.n = -1
console.log(`需求三:${data3.n},设置为 -1 失败`)
data3.n = 1
console.log(`需求三:${data3.n},设置为 1 成功`)

但是如果下列方式传参,还是能修改属性值

let myData5 = {n:0}
let data5 = proxy2({ data:myData5 })

因此我们将data.n的值赋值给一个变量存放,并删除data.n

function proxy2({data}){
    let value = data.n
  Object.defineProperty(data, 'n', {
    get(){
      return value
    },
    set(newValue){
      if(newValue<0)return
      value = newValue
    }
  })

Vue中对数据做了类似的处理,对myData对象的属性读写,全部让vm对象代理,使其具有非侵入式功能,并对所有data属性监控。 Vue的data是响应式的,修改vm.n,那么UI中的n就会响应,Vue2通过 Object.defineProperty来实现数据响应式
每个组件实例都对应一个 watcher 实例,它会在组件渲染的过程中把“接触”过的数据 property 记录为依赖。之后当依赖项的 setter 触发时,会通知 watcher,从而使它关联的组件重新渲染。
对于已经创建的实例,Vue 不允许动态添加根级别的响应式 property。但是,可以使用 Vue.set(object, propertyName, value) 方法向嵌套对象添加响应式 property。您还可以使用 vm.$set 实例方法,这也是全局 Vue.set 方法的别名