Vue中的侦听器属性watch使用的频率还是非常高的,但是对于其中的一些特性使用起来还是比较模糊,没有总结的特别到位。有次和同事为这事争论了半天,看到对方比较强硬自己就怂了,心想难道是我记错了?其实还是因为自己没有深入的探究过,虽然记得,但却没有实际的去验证!为了弄清楚这个问题(为了下次能吵赢 ),今天特意写了一些demo来验证一下。
data中有以下属性,year是普通属性,yearList是数组,obj是一个对象,其中嵌套了一个内层对象。
data () {
return {
year: 2000,
yearList: [2000],
obj: {
name: 'Wang Ming',
age: 18,
favs: ['running', 'swimming', 'dance'],
brother: {
name: 'Wang Gang',
age: 24,
favs: ['computers', 'eating'],
}
}
}
},
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
一、watch监听普通属性
watch监听普通属性时,可以直接使用我们最常用的写法:
监听的属性名(newValue,oldValue){
…
}
watch: {
year (newValue, oldValue) {
console.log('时间又过去了1年')
console.log('新值是' + newValue + ',旧值是' + oldValue)
},
}
- 1
- 2
- 3
- 4
- 5
- 6
当改变year时,就会触发监听器中的函数。
methods: {
grow () {
this.year++
this.obj.age++;
this.yearList.length++
},
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
这种方式也是使用的最普遍的方式。只有当绑定的监听属性发生改变时,才会执行绑定的函数。这里是year改变,就立即执行
二、handler方法和immediate属性。
但只是这样,其实还是远远不够的,例如我们如果想要在watch刚绑定的时候,就执行监听属性中的函数,应该怎么办?
有人说可以在created钩子函数中再定义一个函数来执行,确实可行,但是会显得代码冗余,不便于维护。以后万一要修改监听器属性中的操作,却忘了修改created中的函数,就会导致各种无法预料的问题。
其实watch中有现成的方法!
我们修改一下前面的代码:
watch: {
year: {
handler (newValue, oldValue) {
console.log('时间又过去了1年,今年是' + newValue)
console.log('新值是' + newValue + ',旧值是' + oldValue)
},
immediate: true,
deep: false
},
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
注意到handler了吗,我们给 year绑定了一个handler方法,之前我们写的 year方法其实最终调用的就是这个handler,Vue.js会去处理这个逻辑,最终编译出来的就是这个handler。
而immediate:true代表如果在 wacth 里声明了 year之后,就会立即先去执行里面的handler方法,如果为 false就跟我们以前的效果一样,不会在绑定的时候就执行。
三、deep属性
细心的读者应该发现了,前面的例子中我还写了一个deep:false,其实这个属性默认是为false的。它代表是否深度监听。
什么叫深度监听?
其实在watch中,监听普通的属性变动是没问题的,但是如果监听对象属性或者数组的变动,就会有问题。
watch: {
obj: {
handler (newValue, oldValue) {
console.log('obj更新了新值是' + newValue + ',旧值是' + oldValue)
},
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
这里不写deep,则默认deep为false
下面尝试改变一下obj.age试试。
methods: {
grow () {
this.year++
this.obj.age++;
this.yearList.length++
},
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
会发现obj.age改变到21了,但是一直没有执行obj监听属性中绑定的函数。
这种情况就成为浅监听!因为watch无法监听到对象内层中属性的变化,只能监听到整个对象的赋值是否发生变化(后面的例子4会讲到)。
但是如果将deep设置为true
watch: {
obj: {
handler (newValue, oldValue) {
console.log('obj更新了新值是' + newValue + ',旧值是' + oldValue)
},
deep:true
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
再试试,发现果然输出了obj监听属性中绑定的handler
四、前面提到了浅监听只能监听整个对象赋值的变化,这句话怎么理解呢?
其实如果不想设置deep:true属性,又想让对象的监听器产生作用,那么就不应该对 对象的属性直接赋值!而是直接对整个对象重新赋值!
我们先把deep设置为false
obj: {
handler (newValue, oldValue) {
console.log('obj更新了新值是' + newValue + ',旧值是' + oldValue)
},
deep: false
}
- 1
- 2
- 3
- 4
- 5
- 6
methods: {
growup () {
this.obj = {
name: 'Wang Ming',
age: 28,
favs: ['running', 'swimming', 'dance'],
brother: {
name: 'Wang Gang',
age: 24,
favs: ['computers', 'eating'],
}
}
},
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
然后通过growup方法,对对象进行重新赋值,会发现即使deep为false,依然可以触发watch!
但是,如果只是为了修改某个对象的属性,而让整个对象重新赋值,这种方法听起来就挺蠢的!效率低,消耗内存,还容易出错。万一少了赋值了一个属性,导致属性丢失,前面渲染也会出问题。。。所以这事就当了解,除了某些特殊情况下可以用用。
五、watch监听数组的变化。
再细心一点的读者朋友们可能会发现,我前面有一个yearList,变成了[2000,null,null],但是却没有在console中看到监听属性的执行?是我没有定义对应的watch吗?
其实我定义了,但是为了避免让大家误解就没有放出来。
yearList: {
handler (newValue, oldValue) {
console.log('年份列表改变了,新值是' + newValue + ',旧值是' + oldValue)
},
deep: true
},
- 1
- 2
- 3
- 4
- 5
- 6
grow函数来修改数据
grow () {
this.year++
this.obj.age++;
this.yearList.length++
this.yearList[0]=1955
},
- 1
- 2
- 3
- 4
- 5
- 6
大家可以看到,我这里甚至还设置了deep:true,但是依然没有监听到yearList的变动!为什么呢?
其实是因为,我修改yearList的方式是,this.yearList.length++,直接增加了数组的长度,而由于 JavaScript 的限制,Vue 不能检测以下变动的数组:
当你利用索引直接设置一个项时,例如:vm.items[indexOfItem] = newValue
当你修改数组的长度时,例如:vm.items.length = newLength
官方文档对此的说明:
https://cn.vuejs.org/v2/guide/list.html#注意事项
但是如果我使用vuejs提供的变异方法,例如push(),就可以实现监听!
methods: {
grow () {
this.year++
this.obj.age++;
this.yearList.push(this.year)
},
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
如果你想直接修改数组中的某个值,而不是push数据,官方文档给出了2种解决方案
一、Vue.set
Vue.set(vm.items, indexOfItem, newValue)
二、Array.prototype.splice
vm.items.splice(indexOfItem, 1, newValue)
你也可以使用 vm.$set 实例方法,该方法是全局方法 Vue.set 的一个别名:
vm.$set(vm.items, indexOfItem, newValue)
为了解决数组长度变动的问题,你可以使用 splice:
vm.items.splice(newLength)
六、deep:true设置了后,对象中的对象能否监听到?
既然deep:true设置之后,可以直接监听对象属性的变化,那么对象中的对象能监听到吗?
于是我们把obj.age++注释,增加this.obj.brother.age++,看看王明兄弟的年龄改变,是否会触发watch
grow () {
this.year++
// this.obj.age++;
this.yearList.push(this.year)
this.obj.brother.age++
},
- 1
- 2
- 3
- 4
- 5
- 6
结果依然是可以的。
七、只监听对象某个属性变化的优化。
deep的意思就是深入观察,监听器会一层层的往下遍历,给对象的所有属性都加上这个监听器,但是这样性能开销就会非常大了,任何修改obj里面任何一个属性都会触发这个监听器里的 handler。
但是在实际开发过程中,我们很可能只需要监听obj中的某几个属性,这样设置deep:true之后就显得很浪费!
于是我们可以使用字符串形式来优化监听。
前面obj的监听可以去掉了!
watch: {
'obj.age': {
handler (newValue, oldValue) {
console.log('王明的年龄更新了新值是' + newValue + ',旧值是' + oldValue)
},
immediate: true
},
'obj.brother.age': {
handler (newValue, oldValue) {
console.log('王刚的年龄更新了新值是' + newValue + ',旧值是' + oldValue)
},
immediate: true
},
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
测试发现,完全实现监听效果~~
结语:在使用watch时,如果需要监听对象的某具体属性变动,尽量使用字符串形式来优化监听!
还有一种方法是通过借助computed属性来搭桥,将对象的某个属性定义为一个computed属性,然后返回该对象的属性值,最后使用watch来监听该属性以实现监听对象属性的变化。但是我觉得这种方法,过于hack,也增加了代码的复杂度,就不在这里提倡了。
终于写完了,遇到watch相关的问题再不怕了!下次吵架我一定不认怂
参考文章: https://blog.csdn.net/wandoumm/article/details/80259908