前置知识
两种对象属性描述
对象里的属性可以分为两种形式,一种是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都是一样的,这样的话,监听不就没有意义了?
查看官方文档,也有关于这方面的描述:
文档上只是说了结果,并没有说为什么。于是我谷歌了一下,觉得下面这篇讲的比较有道理
看完以后,发现 读源码 其实挺有意思的,可惜自己还是太菜了,等以后有机会 可以尝试一下。
过细的原因就不详说,这里就根据自己理解总结一下:
尤大在设计这个功能的时候,出于某种原因(有说是JavaScript的限制,有说是性能问题),并没有将对象/数组的每个属性都过滤成getter/setter。如果没有深入过滤对象的每个属性,那么只能监听到对象的变化,而JavaScript里对象的赋值是引用赋值,虽然属性变化了,但是它引用的地址却一直没有变化,这样的话,当对象的属性值改变了,Vue虽然知道它改变了,但也只能循着引用地址去获得对象,可此时对象的属性的值已经改变了,因此Vue并不能得到变异之前的值。
(可能理解有出入,如果有错,烦请指正)
解决方法
既然 watch
无法在变异对象或数组时监听新旧值,那么我们可以先使用JSON.parse()
来浅复制一遍data对象,然后在复制的对象上修改,完了重新赋值给该data对象,这样变化前后两个对象是完全不一样的,因为它们的引用地址完全不一样(这样改变data的方式有点像react,当然 原理是不同的 ..),Vue可以循着两个引用地址获得新旧两个value,从而完美实现 对象变动的watch监听。