hello,我是小索奇,精心制作的Vue系列教程持续更新哈,涵盖大量的经验和示例,由浅入深进行讲解,想要学习&巩固&避坑就一起学习吧~

计算和侦听属性

计算属性

重点概要

定义:要用的属性不存在,需要通过已有属性计算得来

原理:底层借助了Objcet.defineproperty()方法提供的getter和setter来计算属性计算属性会自动找到getter并调用,拿到返回值放到VM身上

关于get函数

  • get初次读取时会执行一次
  • 当依赖的数据发生改变时会被再次调用
  • 与methods实现相比,内部有缓存机制,当我们再次调用get时,直接走缓存了,效率更高,调试时也直接显示computed

那么我们需要改值的时候呢?

  • 计算属性最终会出现在VM上,利用this进行读取使用即可
  • 如果计算属性要被修改,那必须写set函数去响应修改,且set中要引起计算时依赖的数据发生改变
  • 如果计算属性确定不考虑修改,可以使用计算属性的简写形式

代码案例

<body>
    <div id="root">
    
        <input type="text" v-model="firstName"> <br/><br/>
        <input type="text" v-model="lastName" > <br/><br/>
        <h1>{{name}}</h1>
    </div>
   <script>
    Vue.config.productionTip = false
       const vm = new Vue({
            el:'#root',
            data:{
                firstName:'即兴',
                lastName:'小索奇'
            },
            computed:{
                name:{
                    get(){
       return this.firstName + '-' + this.lastName
                  
                    set(value){
                        console.log(VM)
                        const arr = value.split('-')
                        this.firstName = arr[0]
                        this.lastName = arr[1]
                    }
                }
            }
        })
   </script>
</body>

【Vue】深究计算和侦听属性的原理_数据

image-20230819013138164

<body>
    <div id="root">
    
            <input type="text" v-model="firstName"> <br/><br/>
         <input type="text" v-model="lastName"> <br/><br/>
            <h1>{{name}}</h1>
    </div>
   <script>
    Vue.config.productionTip = false
       const vm = new Vue({
            el:'#root',
            data:{
                firstName:'即兴',
                lastName:'小索奇'
            },
            computed:{
                name:{
                    get(){
                        return this.firstName + '-' + this.lastName
                    },
                    set(value){
                        // console.log(VM)
                        const arr = value.split('-')
                        this.firstName = arr[0]
                        this.lastName = arr [1]
                    }
                }
            }
        })
   </script>
</body>

计算属性简写

  • 在实际应用过程中计算属性一般是仅读取展示,不修改的
  • 当你确定只读不更改,那么才可以用简写函数

执行流程:执行完name函数,往VM身上放name这个属性,它的值是函数返回的结果

computed:{
          name(){
             console.log('简写形式,等同于get ')
             return this.firstName + '-' + this.lastName
           }, 
         }

调用时用name即可不用加括号name()-> x

拓展

还可以方法调用,为什么不用呢?看下例

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <script type="text/JS" src="./js/vue.js"></script>
</head>
<body>
    <div id="root">
    
        <input type="text" v-bind:value="firstName"> <br><br>
        <input type="text" :value="lastName" ><br><br>
        <button> 
            <h1>{{name()}}</h1>
        </button>
    </div>
   <script>
    Vue.config.productionTip = false
        new Vue({
            el:'#root',
            data:{
                firstName:'即兴',
                lastName:'小索奇'
            },
            methods:{
                name(){
                    return this.firstName + this.lastName
                }
                
            }
        })
   </script>
</body>
</html>

为什么不用?

因为这样更新频率极高,每一次更改值都会重新调用方法解析模板;效率低下

侦听属性

  • 侦听属性称为监视属性也是一样的

在Vue.js中,侦听属性通常是通过"监视器"(Watchers)来实现的监视器允许你在Vue实例中监听一个特定的数据属性,然后在属性值发生变化时执行相应的逻辑这可以用来在属性变化时执行异步操作、更新其他数据,或者触发其他行为

当被监视的属性变化时,回调函数自动调用

侦听属性的定义方式有两种:声明式和编程式下面详细解释这两种方式以及如何使用侦听属性:

1. 侦听属性:

watch选项来对一个或多个侦听属性每个侦听属性都对应一个属性名,以及一个处理函数,当这个属性发生变化时,处理函数会被触发

<body>
  <div id="root">
    <h2>今天天气很{{ info }}</h2>
    <button @click="changeWeather">切换天气</button>
  </div>
</body>

<script type="text/JS">
  Vue.config.productionTip = false;

  const vm = new Vue({
    el: '#root',
    data: {
      isHot: true,
    },
    computed: {
      info() {
        return this.isHot ? '炎热' : '凉爽';
      },
    },
    methods: {
      changeWeather() {
        this.isHot = !this.isHot;
      },
    },
  });
  watch:{
  isHot:{
   immediate:true, 
   handler(newValue,oldValue){
   console.log('isHot被修改了',newValue,oldValue)
    }
   }
  } 
</script>

2. 侦听属性:

还可以通过$watch方法在Vue实例中编程式地设置侦听属性 ,调用VM的方法$watch

vm.$watch('isHot', {
  // 初始化立即执行handler
    immediate: true,
    // 同理,都能接受新旧值进行处理
    handler(newValue, oldValue) {
      console.log('isHot被修改了', newValue, oldValue);
    },
  });

无论是声明式还是编程式的侦听属性,你都可以在侦听属性的处理函数中执行任何你需要的操作,比如发起网络请求、更新其他数据属性、触发方法等

需要注意的是,尽管侦听属性是强大的工具,但过多的侦听属性可能会导致代码变得复杂且难以维护在一些情况下,你可以使用计算属性(Computed Properties)来取代侦听属性,以提高代码的可读性和性能

使用watch选项或$watch方法都可以设置侦听属性,从而在属性变化时触发处理函数

深度监视

简单概括就是,深度监视是指对一个对象的所有嵌套属性进行观察和监听

  1. 使用方法:在 watch 选项中,为要监视的对象设置 deep: true
  2. 工作原理:当设置为 deep: true 时,Vue 会递归地遍历对象的所有子属性,确保无论如何修改对象的任何嵌套属性,都会触发回调函数
  3. 注意事项:因为深度监视需要递归地监听对象的所有子属性,所以它可能会对性能产生影响,特别是当对象结构复杂、嵌套层次深时
  4. 使用场景:当你需要响应对象内部属性的变化,而不仅仅是对象本身的引用变化时,使用深度监视

一个小案例

<body>
  <div id="root">
   <h2>今天天气很{{info}}</h2>
   <button @click="changeWeather">切换天气</button>
   <hr/>
   <h3>a的值是:{{numbers.a}}</h3>
   <button @click="numbers.a++">点我让a+1</button>
   <h3>b的值是:{{numbers.b}}</h3>
   <button @click="numbers.b++">点我让b+1</button>
   <button @click="numbers = {a:666,b:888}">改变numbers对象</button>
   {{numbers.c.d.e}}
  </div>
 </body>

 <script type="text/JS">
  Vue.config.productionTip = false //阻止 vue 在启动时生成生产提示
  
  const vm = new Vue({
   el:'#root',
   data:{
    isHot:true,
    numbers:{
     a:1,
     b:1,
     c:{
      d:{
       e:100
      }
     }
    }
   },
   computed:{
    info(){
     return this.isHot ? '炎热' : '凉爽'
    }
   },
   methods: {
    changeWeather(){
     this.isHot = !this.isHot
    }
   },
   watch:{
    isHot:{
     handler(newValue,oldValue){
      console.log('isHot被修改了',newValue,oldValue)
     }
    },
    //监视多级结构中某个属性的变化
    /* 'numbers.a':{
     handler(){
      console.log('a被改变了')
     }
    } */
    //监视多级结构中所有属性的变化
    numbers:{
     deep:true,
     handler(){
      console.log('numbers改变了')
     }
    }
   }
  })

 </script>
</html>

注意:当彻底替换掉numbers的时候,是没有d的,打开调试页面,所以点击彻底替换之后d会报错

deep:true这一项不开启的话,当numbers中的项(如a、b改变)number不会变化,当我们开启的话,就代表监视多级结构中属性的变化

侦听属性简写

  • 当配置项中只有handler时才可以简写,要是需要其它配置的话(比如deep)则不能够简写(一般默认写这个即可)
watch: {
  isHot(newValue, oldValue) {
    console.log('isHot被修改了', newValue, oldValue)  
  }
}

使用VM方法调用

//正常写法
  /* vm.$watch('isHot',{
   immediate:true, //初始化时让handler调用一下
   deep:true,//深度监视
   handler(newValue,oldValue){
    console.log('isHot被修改了',newValue,oldValue)
   }
  }) */

  //简写
  /* vm.$watch('isHot',(newValue,oldValue)=>{
   console.log('isHot被修改了',newValue,oldValue,this)
  }) */

对比侦听属性 & 计算属性

计算属性主要适用于:

  • 需要对数据进行转换或格式化后再显示
  • 需要使用多个数据计算得出一个结果进行显示
  • 需要缓存结果,避免每次重复计算
  • 浮动面板或列表中需要对每个元素做重复计算

侦听属性主要适用于:

  • 需要在数据变化时触发副作用,比如调用接口、导航路由等(后期会提到)
  • 需要进行异步操作或开销较大的处理(比如使用定时器)
  • 需要参数化或复用监听逻辑
  • 当值的变化需要改变多个状态时

简单来说,如果单纯显示或格式化数据,使用计算属性可以简化代码并获得性能优势

如果数据变化需要触发复杂逻辑或副作用,监听属性会更合适

如果一个业务逻辑这两种都能实现的话,优先考虑计算属性

拓展

下列代码的this输出什么?

watch:{
            setTimeout(()=>{
                this.name = this.firstName + '-' + this.lastName
            },1000)
        }

这里的this指向Vue实例

Q:如果把setTimeOut剪头函数改为普通函数即:setTimeout(function(){}),那么this指向是哪里

A:指向window!如果定时器是JS引擎调用的,箭头函数没有自己的this指向,那么就会往外找,从而找到VM

具体来说普通函数定义的方法,它的 this 会绑定当前 Vue 实例

tips

  • computed能完成的功能,watch都可以完成
  • watch能完成的功能,computed不一定能完成,例如:watch可以进行异步操作,上面定时器例子就阐明了这一点

两个重要的小原则:

  • 被Vue管理的函数,最好写成普通函数,这样this的指向才是VM 或 组件实例对象,可以更好的调用VM
  • 不被Vue所管理的函数(定时器的回调函数、ajax的回调函数等、Promise的回调函数),最好写成箭头函数,this的指向也就不会乱套了