Vue中的监听器属性watch,监听对象的一些深入研究。

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'],
        }
      }
    }
  },

一、watch监听普通属性
watch监听普通属性时,可以直接使用我们最常用的写法:
监听的属性名(newValue,oldValue){

}

  watch: {
    year (newValue, oldValue) {
      console.log('时间又过去了1年')
      console.log('新值是' + newValue + ',旧值是' + oldValue)
    },
   }

当改变year时,就会触发监听器中的函数。

  methods: {
    grow () {
      this.year++
      this.obj.age++;
      this.yearList.length++
    },
  }

这种方式也是使用的最普遍的方式。只有当绑定的监听属性发生改变时,才会执行绑定的函数。这里是year改变,就立即执行
在这里插入图片描述

二、handler方法和immediate属性。
但只是这样,其实还是远远不够的,例如我们如果想要在watch刚绑定的时候,就执行监听属性中的函数,应该怎么办?
有人说可以在created钩子函数中再定义一个函数来执行,确实可行,但是会显得代码冗余,不便于维护。以后万一要修改监听器属性中的操作,却忘了修改created中的函数,就会导致各种无法预料的问题。

其实watch中有现成的方法!
我们修改一下前面的代码:

  watch: {
    year: {
      handler (newValue, oldValue) {
        console.log('时间又过去了1年,今年是' + newValue)
        console.log('新值是' + newValue + ',旧值是' + oldValue)
      },
      immediate: true,
      deep: false
    },
   }

注意到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)
      },
    }
   }

这里不写deep,则默认deep为false
下面尝试改变一下obj.age试试。

  methods: {
    grow () {
      this.year++
      this.obj.age++;
      this.yearList.length++
    },
  }

会发现obj.age改变到21了,但是一直没有执行obj监听属性中绑定的函数。
在这里插入图片描述
这种情况就成为浅监听!因为watch无法监听到对象内层中属性的变化,只能监听到整个对象的赋值是否发生变化(后面的例子4会讲到)。
但是如果将deep设置为true

  watch: {
     obj: {
      handler (newValue, oldValue) {
        console.log('obj更新了新值是' + newValue + ',旧值是' + oldValue)
      },
      deep:true
    }
   }

再试试,发现果然输出了obj监听属性中绑定的handler
在这里插入图片描述
四、前面提到了浅监听只能监听整个对象赋值的变化,这句话怎么理解呢?
其实如果不想设置deep:true属性,又想让对象的监听器产生作用,那么就不应该对 对象的属性直接赋值!而是直接对整个对象重新赋值!

我们先把deep设置为false

    obj: {
      handler (newValue, oldValue) {
        console.log('obj更新了新值是' + newValue + ',旧值是' + oldValue)
      },
      deep: false
    }
  methods: {
    growup () {
      this.obj = {
        name: 'Wang Ming',
        age: 28,
        favs: ['running', 'swimming', 'dance'],
        brother: {
          name: 'Wang Gang',
          age: 24,
          favs: ['computers', 'eating'],
        }
      }
    },
  }

然后通过growup方法,对对象进行重新赋值,会发现即使deep为false,依然可以触发watch!
在这里插入图片描述

但是,如果只是为了修改某个对象的属性,而让整个对象重新赋值,这种方法听起来就挺蠢的!效率低,消耗内存,还容易出错。万一少了赋值了一个属性,导致属性丢失,前面渲染也会出问题。。。所以这事就当了解,除了某些特殊情况下可以用用。

五、watch监听数组的变化。
再细心一点的读者朋友们可能会发现,我前面有一个yearList,变成了[2000,null,null],但是却没有在console中看到监听属性的执行?是我没有定义对应的watch吗?
其实我定义了,但是为了避免让大家误解就没有放出来。

    yearList: {
      handler (newValue, oldValue) {
        console.log('年份列表改变了,新值是' + newValue + ',旧值是' + oldValue)
      },
      deep: true
    },

grow函数来修改数据

    grow () {
      this.year++
      this.obj.age++;
      this.yearList.length++
      this.yearList[0]=1955
    },

在这里插入图片描述
大家可以看到,我这里甚至还设置了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)
    },
  }

在这里插入图片描述
如果你想直接修改数组中的某个值,而不是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++
    },

结果依然是可以的。
在这里插入图片描述

七、只监听对象某个属性变化的优化。
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
    },
  }

测试发现,完全实现监听效果~~
在这里插入图片描述

结语:在使用watch时,如果需要监听对象的某具体属性变动,尽量使用字符串形式来优化监听!

还有一种方法是通过借助computed属性来搭桥,将对象的某个属性定义为一个computed属性,然后返回该对象的属性值,最后使用watch来监听该属性以实现监听对象属性的变化。但是我觉得这种方法,过于hack,也增加了代码的复杂度,就不在这里提倡了。

终于写完了,遇到watch相关的问题再不怕了!下次吵架我一定不认怂

参考文章: https://blog.csdn.net/wandoumm/article/details/80259908

猜你喜欢

转载自blog.csdn.net/SilenceJude/article/details/86608018