Vue的数据响应与watch

271 阅读3分钟

前置知识

MDN|object.defineProperty()

两种对象属性描述

对象里的属性可以分为两种形式,一种是key-value形式的数据描述符,而另外一种就是由getter-setter函数对描述的存取描述符。

Vue实现响应式效果

在Vue里,就是通过遍历data,使用object.defineProperty()来将数据转换成data的存取描述符,然后通过getter-setter来监测数据的变化,从而达到响应式的效果。

那么问题来了,基于这样的设计限制,我们在改变data的时候,自然会有一些限制:

当data为对象时,我们不能动态地添加对象属性。这是由于动态新增的属性默认是数据描述符,无法通过getter/setter来监测属性的变化。

解决方法

  • 在初始化data时,给该属性一个初始化的值,可以用null、空字符串或其他值
  • 使用vue的set API
# data
{
    family:{
        father:{
            name:'Jack'
        },
        mother:{
            name:'Rose',
            pregnant:false
        }
    }
}
# methods
{
    born(){
        if(mother.pregnant){
            setTimeout(()=>{ 
                this.$set(this.family,'son',{name:'Jason'}) // vm.$set(target,key,value)
            },100000)
        }
    }
}
  • 直接赋值一个新的对象给该data,还是上面那个例子
# data
   {
       family:{
           father:{
               name:'Jack'
           },
           mother:{
               name:'Rose',
               pregnant:false
           }
       }
   }
   # methods
   {
       born(){
           let copyFamily = JSON.parse(JSON.stringfy(this.family))
           if(mother.pregnant){
               setTimeout(()=>{ 
                   copyFamily[son] = {
                       name:'Jason',
                       age:0
                   }
                   this.family = copyFamily
               },100000)
           }
       }
   }

至此,我以为我已经掌握了 Vue 响应式,直到我又遇到了一个新的坑 :point_down:

Vue watch 无法监测对象的变动

从一个项目说起:点我查看代码

探索原因

在这里,dataList是一个数组,通过computed 可以轻易地根据数据响应来实时更新一些页面需要显示的数据。

但,如果使用watch来监听dataList可以发现他的前后value都是一样的,这样的话,监听不就没有意义了?

查看官方文档,也有关于这方面的描述:

文档上只是说了结果,并没有说为什么。于是我谷歌了一下,觉得下面这篇讲的比较有道理

记一次思否问答的问题思考:Vue为什么不能检测数组变动

看完以后,发现 读源码 其实挺有意思的,可惜自己还是太菜了,等以后有机会 可以尝试一下。

过细的原因就不详说,这里就根据自己理解总结一下:

尤大在设计这个功能的时候,出于某种原因(有说是JavaScript的限制,有说是性能问题),并没有将对象/数组的每个属性都过滤成getter/setter。如果没有深入过滤对象的每个属性,那么只能监听到对象的变化,而JavaScript里对象的赋值是引用赋值,虽然属性变化了,但是它引用的地址却一直没有变化,这样的话,当对象的属性值改变了,Vue虽然知道它改变了,但也只能循着引用地址去获得对象,可此时对象的属性的值已经改变了,因此Vue并不能得到变异之前的值。

(可能理解有出入,如果有错,烦请指正)

解决方法

既然 watch无法在变异对象或数组时监听新旧值,那么我们可以先使用JSON.parse()来浅复制一遍data对象,然后在复制的对象上修改,完了重新赋值给该data对象,这样变化前后两个对象是完全不一样的,因为它们的引用地址完全不一样(这样改变data的方式有点像react,当然 原理是不同的 ..),Vue可以循着两个引用地址获得新旧两个value,从而完美实现 对象变动的watch监听。

解决方法