首页 前端知识 vue2中的watch(侦听器)讲解,以及解决深度监听新值和旧值相同的两种方案(手写深拷贝和JSON.parse())。

vue2中的watch(侦听器)讲解,以及解决深度监听新值和旧值相同的两种方案(手写深拷贝和JSON.parse())。

2024-05-25 09:05:15 前端知识 前端哥 381 283 我要收藏

目录

一:什么是watch?

二:watch的基础使用 

 1.最基本的使用

 2.简写形式

三:watch中的immediate和deep属性

 1.immediate属性

 2.deep属性

 3.解决深度监听新旧值相同的问题        

       1)使用序列化和反序列化。

        2)手写深拷贝算法


一:什么是watch?

        相信大家在开发项目中,有时候会遇到一些需求,是当一个数据改变之后进行一些操作,这个时候有些人会设置一个定时器,周期性的去循环访问,当发现数据发生了改变后执行操作。但是这种操作方式会导致系统资源的浪费,以及更新的不及时等。因此vue通过watch(侦听器)提供了一个更通用的方法来响应数据的变化,当需要在数据变化时执行异步或开销较大的操作时,这个方式是最有用的。

        核心原理就是对传入Vue options的watch对象里面的所有的属性,都添加一个自定义watcher,收集对应属性的依赖,然后当这个依赖项更新的时候,不仅通知它的渲染watcher(可能没有),也会通知当前的这个自定义watcher,从而叫到你定义的callback,完成函数内的逻辑。

二:watch的基础使用 

 1.最基本的使用

         下方代码是watch的一个基础使用,可以看到当第一次进入页面的时候,并没有触发,而点击按钮的时候,name的值发生了改变,被watch监听到,因此执行了下面的输出逻辑。

<template>
  <div class="home">
    <el-button @click="name='李四'">改变</el-button>
  </div>
</template>

<script>
  export default {
    data() {
      return {
        name: '张三'
      }
    },
    //使用watch监听器,当不点击按钮的时候不会触发
    watch: {
      name: { //这里是要监听的变量名
        handler(newValue, oldValue) { //这里的第一个参数是改变后的值,第二个参数是原来的值
          console.log('改后的值是:' + newValue)
          console.log('原来的值是:' + oldValue)
        }
      }
    }
  }
</script>
运行效果

 2.简写形式

        当只是如上所示,简单地进行监听的时候,为了方便,我们可以直接使用简写形式。

<template>
  <div class="home">
    <el-button @click="name='李四'">改变</el-button>
  </div>
</template>

<script>
  export default {
    data() {
      return {
        name: '张三'
      }
    },
    //使用watch监听器,当不点击按钮的时候不会触发
    watch: {
      name(newValue, oldValue) { //这里的第一个参数是改变后的值,第二个参数是原来的值
        console.log('改后的值是:' + newValue)
        console.log('原来的值是:' + oldValue)
      }
    }
  }
</script>

三:watch中的immediate和deep属性

 1.immediate属性

         在上述代码中,我们可以发现,当watch第一次绑定数据的时候并没有触发监听效果,这是watch中的一个特性,但同样的,我们可以使用immediate属性改变这种特性。

<template>
  <div class="home">
    <el-button @click="name='李四'">改变</el-button>
  </div>
</template>

<script>
  export default {
    data() {
      return {
        name: '张三'
      }
    },
    //使用watch监听器,当不点击按钮的时候不会触发
    watch: {
      name: {
        handler(newValue, oldValue) {//这里的第一个参数是改变后的值,第二个参数是原来的值
          console.log('改后的值是:' + newValue)
          console.log('原来的值是:' + oldValue)
        },
        immediate: true
      }
    }
  }
</script>
运行效果

 2.deep属性

         在使用watch的过程中,如果定义了一个对象,这时候我们会发现使用一般的watch监听,是无法监听对象内部的改变的,这时候我们需要使用depp属性进行一个深度监听。

        比如在以下代码中,因为watch所监听的是一个person对象,当点击按钮改变之后,会发现没有任何执行效果。也就是监听失败。

<template>
  <div class="home">
    <el-button @click="person.name='李四'">改变</el-button>
  </div>
</template>

<script>
  export default {
    data() {
      return {
        person: {
          name:'张三'
        }
      }
    },
    //使用watch监听器,当不点击按钮的时候不会触发
    watch: {
      person: {
        handler(newValue, oldValue) {//这里的第一个参数是改变后的值,第二个参数是原来的值
          console.log('改后的值是:' + newValue.name)
          console.log('原来的值是:' + oldValue.name)
        }
      }
    }
  }
</script>

         所以我们在上述代码中加入depp:true,形成下面的代码,这时候我们可以在控制台看到是能够成功执行的。

<template>
  <div class="home">
    <el-button @click="person.name='李四'">改变</el-button>
  </div>
</template>

<script>
  export default {
    data() {
      return {
        person: {
          name:'张三'
        }
      }
    },
    //使用watch监听器,当不点击按钮的时候不会触发
    watch: {
      person: {
        handler(newValue, oldValue) {//这里的第一个参数是改变后的值,第二个参数是原来的值
          console.log('改后的值是:' + newValue.name)
          console.log('原来的值是:' + oldValue.name)
        },
        deep:true//深度监听
      }
    }
  }
</script>
运行效果

 3.解决深度监听新旧值相同的问题        

        但是在上面的运行效果中,我们可以发现改变前和改编后的值都是一样的,这显然不符合我们的初衷,失去了oldValue的作用,当然我们可以把监听的字符串person变成’person.name‘,但是这样又不属于监听对象了,而是回到了最初的监听字符串。

        我查了一下vue的官方文档,对watch的解释如下

在变异 (不是替换) 对象或数组时,旧值将与新值相同,因为它们的引用指向同一个对象/数组。Vue 不会保留变异之前值的副本。

        通过官方解释,我们有以下两种方式解决该问题:

       1)使用序列化和反序列化。

<template>
  <div class="home">
    <el-button @click="person.name='李四'">改变</el-button>
  </div>
</template>

<script>
  export default {
    data() {
      return {
        person: {
          name:'张三'
        }
      }
    },
    //使用watch监听器,当不点击按钮的时候不会触发
    watch: {
      newPerson: {
        handler(newValue, oldValue) {//这里的第一个参数是改变后的值,第二个参数是原来的值
          console.log('改后的值是:' + newValue.name)
          console.log('原来的值是:' + oldValue.name)
        },
        deep:true,//深度监听
      }
    },
    computed:{
    // 使用计算属性进行深拷贝
      newPerson(){
        return JSON.parse(JSON.stringify(this.person))
      }
    }
  }
</script>
运行效果

        这种方式优点是直接简单,对于程序员而言代码行数更少,但是缺点在于这种深拷贝方式容易带来性能的问题,并且会丢失原有的原型链,在如unidentified等特殊值时会报错,将NaN序列化成null等问题。所以通常而言,用第二种方法会更好一些

        2)手写深拷贝算法

<template>
  <div class="home">
    <el-button @click="person.name='李四'">改变</el-button>
  </div>
</template>

<script>
  export default {
    data() {
      return {
        person: {
          name: '张三'
        }
      }
    },
    //使用watch监听器,当不点击按钮的时候不会触发
    watch: {
      newPerson: {
        handler(newValue, oldValue) { //这里的第一个参数是改变后的值,第二个参数是原来的值
          console.log('改后的值是:' + newValue.name)
          console.log('原来的值是:' + oldValue.name)
        },
        deep: true, //深度监听
      }
    },
    computed: {
      // 使用计算属性进行深拷贝
      newPerson() {
        // 使用自己手写的深克隆方法,效率更高
        return this.deepClone(this.person)
      }
    },
    methods: {
      //下面这个是手写深拷贝
      deepClone(source) {
        let target = null;
        if (!this.isArray(source) && !this.isObject(source)) {
          target = source;
        }
        if (this.isArray(source)) {
          target = [];
          for (let i = 0; i < source.length; i++) {
            target[i] = this.deepClone(source[i]);
          }
        }
        if (this.isObject(source)) {
          target = {};
          for (let key in source) {
            target[key] = this.deepClone(source[key]);
          }
        }
        return target;
      },

      isArray(value) {
        return Object.prototype.toString.call(value) === '[object Array]';
      },

      isObject(value) {
        return Object.prototype.toString.call(value) === '[object Object]';
      }
    }
  }
</script>

         这里运行效果和方案一的效果是一样的,所以不再附加运行截图。但是运行时间上可以看到,将点击事件的代码修改成方法,并且使用console.time()来计算时间,如下代码所示

<template>
  <div class="home">
    <!-- 这里的@click直接变成一个方法 -->
    <el-button @click="handleClick">改变</el-button>
  </div>
</template>


<!-- script修改部分如下 -->

<script>
  export default {
    ...
    watch: {
      newPerson: {
        handler(newValue, oldValue) { //这里的第一个参数是改变后的值,第二个参数是原来的值
          console.log('改后的值是:' + newValue.name)
          console.log('原来的值是:' + oldValue.name)
          console.timeEnd('process')// 《---新增代码(关闭计时器)
        },
        deep: true, //深度监听
      }
    },

    ...

    },
    methods: {
      handleClick(){// 《---新增代码
        console.time('process')//打开计时器
        this.person.name='李四'
      },
      ....
    }
  }
</script>

添加上述代码后,再次执行两种方式的代码,执行时间如下

JSON.parse()的执行时间

手写深拷贝的执行时间

         通过上面两张图的对比,我们可以清楚地看到,使用JSON.parse()的执行时间是比手写深拷贝要久的,因此在大项目中,手写的执行效率往往更具有优势。

转载请注明出处或者链接地址:https://www.qianduange.cn//article/9471.html
标签
评论
会员中心 联系我 留言建议 回顶部
复制成功!