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

2023-09-22 13:02:37

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>

image-20230819013138164

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的指向也就不会乱套了

更多推荐

【Vue】避免Vue组件中常见的props默认值陷阱

1.对象和数组默认值的共享问题当你将一个对象或数组作为props的默认值时,它们会在组件的所有实例之间共享。这意味着如果一个组件修改了这个默认值,其他组件也会受到影响,因为它们共享同一个引用。陷阱:props:{userInfo:{type:Object,default:{}}}问题:如果一个组件修改了userInfo

基于vue3 + ant-design 自定义SVG图标iconfont的解决方案;ant-design加载本地iconfont.js不显示图标问题

基于vue3+ant-design自定义SVG图标iconfont的解决方案;ant-design加载本地iconfont.js不显示图标问题一、准备工作1、首先去阿里巴巴矢量图标库自定义添加自己的图标;网站地址https://www.iconfont.cn/整个步骤是:选择图标–添加到项目-项目设置-下载到本地已经选

Web Storage是什么?Web Storage详解

WebStorag是HTML5引入的一个非常重要的功能,可以将数据存储在本地,如保存用户的偏好设置、复选框的选中状态、文本框默认填写的值等。用户在浏览器中刷新网页时,网页通过WebStorage就可以知道用户之前所做的一些修改,而不需要将用户修改的内容存储在服务器端。WebStorage类似于Cookie,但相比Coo

【docker安装Mysql并配置主从复制】

Mysql主从复制目的:是为了后面naocs集群的服务配置做准备工作准备工作准备至少两台虚拟机或服务器,安装好了docker,找到他们的ip地址后面操作都用xshell操作来代替拉取并启动mysql镜像和容器主机的命令为mysql01,对外端口用3310来连接dockerrun-d-p3310:3306-v/home/

ubuntu在线直接升级

前几天VMware上安装了ubuntu,当时的内核版本支持(ipguard,加密软件),后来ubuntu自动升级了linux内核,导致加入软件不支持,无法访问加密文件了。后来加密软件商更新了软件,但还是赶不上linux内核更新速度,还是不能用。之前我写过手动升级内核的方法,实在有些复杂,所以借助chatgpt的提示和摸

Vuex —— 状态管理 | Module

在前面讲到了关于Vuex数据状态管理的内容,讲了Vuex的五大核心属性,在这五大核心属性中就state、mutation和actions在前面介绍Vuex状态管理和讲Vuex中的同步和异步操作已经比较熟悉了,getter是基于state的计算属性,vue中computed从data中派生出的计算属性,vuex中gett

【校招VIP】java语言考点之嵌套类&内部类

考点介绍:嵌套类&内部类问题在校招面试中经常出现。以在一个类的内部定义另一个类,这种类称为嵌套类(nestedclasses),它有两种类型:静态嵌套类和非静态嵌套类。静态嵌套类使用很少最重要的是非静态嵌套类,也即是被称作为内部类(inner)。java语言考点之嵌套类&内部类-相关题目及解析内容可点击文章末尾链接查看

如何搭建Linux环境

W...Y的主页😊代码仓库分享💕当我们想要搭建一个Linux系统,我们应该怎么使用呢?今天我就带领大家搭建Linux系统!!!目录Linux环境安装双系统(不推荐)powwershell(不推荐)虚拟机+centos7镜像使用云服务器(推荐)XShell下的复制粘贴Linux环境安装双系统(不推荐)在计算机上安装L

Linux MQTT智能家居(源码使用分析)

文章目录前言一、连接服务器1.初始化客户端2.设置端口号设置IP地址3.连接服务器二、发布消息三、订阅消息总结前言本篇文章开始我们来分析一下大佬写的MQTT源码,并且来看看怎么样使用MQTT连接到服务器。MQTT源码地址:源码地址这里找到源码中的test.c进行分析:一、连接服务器1.初始化客户端首先使用mqtt_le

Linux MQTT智能家居(ubantu和ARM中使用MQTT)

文章目录前言一、在ubantu中使用MQTT1.安装cmake2.编译MQTT库二、在ARM中使用MQTT三、使用自己的服务器四、ARM板服务器MQTTX三者关系五、MQTTX的使用六、ARM使用MQTT的方法1.修改MQTT源码2.使用库3.把MQTT源码加入到自己的工程总结前言本篇文章将会带大家在ubantu和AR

elementui-slider 滑动时会重置为0的问题解决

文章目录问题描述:问题排查问题解决总结问题描述:首次打开有elementui-slider的页面,不管滑动哪个滑块,滑动时都会自动归0(划得动,但是会自动回到最左侧0的位置),但是他确实触发了change函数。问题排查尝试了很多方法,包括将slider的值设置为number类型,虽然它本身就是number,用了一个干净

热文推荐