数据响应式即是当页面当中的数据发生改变的时候,vue 会跟新数据并且重新渲染页面,改变页面上原先对应的数值。
那么 vue 又是怎样知道一个数据发生了变化呢?

vue 对数据的监听

通过 Object.defineProperty 可以为对象 data 添加属性 value。这个 value 同样支持函数的形式,这样就可以添加上 get / set, 用于读写对象中的属性值,并对其进行监听。其中 set 函数又支持写入一个判断的方式,让特定的数值才被允许写入。

  1. let data2 = {}
  2. data2._n = 0 // _n 用来存储 n 的值
  3. Object.defineProperty(data2, 'n', {
  4. get(){
  5. return this._n
  6. },
  7. set(value){
  8. if(value < 0) return
  9. this._n = value
  10. }
  11. })

这样可以监听 data2 里面的 n 的变化。但是这样存在一些小问题。
比如别人可以通过修改存储 n 的 _n 来绕过 Object.defineProperty 对 data 作出修改。为了让所有的数据变化都能让 vue 知道,于是需要添加代理来专门负责对象属性的读写。

  1. let myData5 = {n:0}
  2. let data5 = proxy2({ data:myData5 }) // 括号里是匿名对象,无法访问
  3. function proxy2({data}/*解构赋值*/){
  4. // 这里的 'n' 写死了,理论上应该遍历 data 的所有 key,这里做了简化
  5. let value = data.n
  6. Object.defineProperty(data, 'n', {
  7. get(){
  8. return value
  9. },
  10. set(newValue){
  11. if(newValue<0)return
  12. value = newValue
  13. }
  14. })
  15. // 就加了上面几句,这几句话会监听 data
  16. const obj = {}
  17. Object.defineProperty(obj, 'n', {
  18. get(){
  19. return data.n
  20. },
  21. set(value){
  22. if(value<0)return//这句话多余了
  23. data.n = value
  24. }
  25. })
  26. return obj // obj 就是代理
  27. }

这种设计模式当然被应用到了 vue 当中,vue 的写法如下

  1. const vm = new Vue({data:{n:0}})

这里的 vm 便是 data 内对象的代理,会对其的所有属性监控,这样一来 vm 就会掌握 data 的变化,在变化时调用 render(data) ,在页面中重新渲染变化部分的内容。

data 的新增

上面说的监听属性存在一个前提:那就是先得有这个属性,如果监听了一个不存在的属性会怎样?
当然会报错。vue 会给出一个警告。
这时候我们要用到 Vue.set 和 this.$set 来新增一个可供监听的属性:

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

这样就会新增一个 key ,兵器自动创建代理和监听。在值改变的时候触发更新,重新渲染改变的值。

那如果是数组呢

数组没有办法确定之后会有少个数据,所以没有办法将所有的 key 提前声明出来。这时就需要引用到 vue 修改过的数组相关 API:

  • push() —新增
  • pop() —弹出最后一个
  • shift() —弹出首个
  • unshift() —在开头新增
  • splice() —中间删除某个
  • sort() —排序
  • reverse() —逆排序

通过调用这 7个API,会自动地处理代理与监听,并且及时更新页面数据。