Vue 对 data 做了什么


在 Vue 项目中,通过 let vm = new Vue({data: myData}) 就可以在当数据发生变化后,使用到该数据的视图也会相应进行自动更新,这是为什么呢?

本文不从源码分析 Vue 对 data 做了什么(毕竟我也读不懂源码😜),只为记录学习到的研究方法,有时,方法比知识本身更重要不是么(授人以鱼不如授人以渔)

首先,这里卖个关子,撇开 Vue,学几个 ES6 的新语法

getter / setter

get 语法将对象属性绑定到查询该属性时将被调用的函数

当尝试设置属性时,set 语法将对象属性绑定到要调用的函数

let obj = {
  _num: 0,
  get num() { return this._num },
  set num(n) { this._num = n }
}
console.log(obj.num)
obj.num = 1
console.log(obj.num)

大白话讲它们的用法就是 getter 调用时不加括号的函数,setter 用 = xxx 对属性赋值

Object.defineProperty()

Object.defineProperty() 方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回此对象

它的语法

Object.defineProperty(obj, prop, descriptor)

现在有一个需求,data 对象的 n 属性值不能小于0

结合 getter / setter 的用法

let data1 = {}
data1._n = 0 // _n 用来偷偷存储 n 的值
Object.defineProperty(data1, 'n', {
  get(){
    return this._n
  },
  set(value){
    if(value < 0) return
    this._n = value
  }
})
console.log(`需求一:${data1.n}`)
data1.n = -1
console.log(`需求一:${data1.n} 设置为 -1 失败`)
data1.n = 1
console.log(`需求一:${data1.n} 设置为 1 成功`)

需求一.png

倘若这里直接修改 data1._n 呢?

可以发现 data1.n 也被修改了

考虑将 _n 包装为一个匿名对象的属性,通过代理函数访问

代理

let data2 = proxy({ data:{n:0} }) // 括号里是匿名对象,无法访问
function proxy({data}){
  const obj = {}
  Object.defineProperty(obj, 'n', { 
    get(){
      return data.n
    },
    set(value){
      if(value<0)return
      data.n = value
    }
  })
  return obj  // obj 就是代理
}
console.log(`需求二:${data2.n}`)
data2.n = -1
console.log(`需求二:${data2.n},设置为 -1 失败`)
data2.n = 1
console.log(`需求二:${data2.n},设置为 1 成功`)

需求二.png

假如先定义 myData = {n:0},再将 myData 对象传递给匿名对象(data2 = proxy({ data:myData })),则仍可以通过 myData.n 来修改 data2.n 的值,所以需要对代理接收的数据进行监听

数据监听

let myData = {n:0}
let data3 = proxy2({ data:myData })
function proxy2({data}){
  let value = data.n
  Object.defineProperty(data, 'n', {
    get(){
      return value
    },
    set(newValue){
      if(newValue<0)return
      value = newValue
    }
  })
  // 就加了上面几句,这几句话会监听 data
  const obj = {}
  Object.defineProperty(obj, 'n', {
    get(){
      return data.n
    },
    set(value){
      data.n = value
    }
  })
  return obj // obj 就是代理
}
console.log(`需求三:${data3.n}`)
myData.n = -1
console.log(`需求三:${data3.n},设置为 -1 失败了`)
myData.n = 1
console.log(`需求三:${data3.n},设置为 1 成功了`)

需求三.png

Vue 的 data

现在,回过头来看看 let vm = new Vue({data: myData}) 做了什么

  1. vm 成为 myData 的代理,由 vm 全权负责 myData 对象的属性读写

  2. myData 的所有属性进行监听,当 myData 的属性发生变化则通知 vm 调用 render(data) 更新 UI

Vue.set 和 this.$set

看前面 Object.defineProperty() 的用法可以知道必须要有一个 prop 才可以监听代理 obj[prop],在Vue 文档中 [Vue 示例] -> [数据与方法] 一节中也提及

当一个 Vue 实例被创建时,它将 data 对象中的所有的 property 加入到 Vue 的响应式系统中,比如:var vm = new Vue({ data: { a: 1 }})。当这些 property 的值发生改变时,视图将会产生“响应”,即匹配更新为新的值。当这些数据改变时,视图会进行重渲染。值得注意的是只有当实例被创建时就已经存在于 data 中的 property 才是响应式的。也就是说如果你添加一个新的 property,比如:vm.b = 'hi' 那么对 b 的改动将不会触发任何视图的更新。如果你知道你会在晚些时候需要一个 property,但是一开始它为空或不存在,那么你仅需要设置一些初始值。

但如果一开始不知道这个 property,则可以通过 Vue.set()vm.$set() 动态添加

作用:

  • 新增 property

  • 自动创建代理和监听

  • 触发 UI 更新(但不会立即更新)


文章作者: April-cl
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 April-cl !
  目录