掘金 后端 ( ) • 2024-04-03 17:45

1.概述

书接上回,我们讲述了《后端前行Vue之路(二):模版语法之插值与指令》谈到了Vue的模板语法很强大,支持复杂表达式,如下:

<div id="example">
  {{ message.split('').reverse().join('') }}
</div>

模板内的表达式非常便利,但是设计它们的初衷是用于简单运算的。在模板中放入太多的逻辑会让模板过重且难以维护。在上面代码示例中,模板不再是简单的声明式逻辑。你必须看一段时间才能意识到,这里是想要显示变量 message 的翻转字符串。当你想要在模板中的多处包含此翻转字符串时,就会更加难以处理。

Vue提供了计算属性(Computed Properties)和监视属性(Watch Properties) 都是用于响应数据变化的方式,可以解决模版中复杂表达式场景,使得代码逻辑更加整洁明了和可维护。

2.计算属性Computed

定义方式: 计算属性是通过computed选项来定义的,它接受一个对象,对象的每个键都是计算属性的名称,对应的值是一个函数,用于计算属性的值。如上面的翻转字符串复杂表达式可用计算属性完成如下:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8" />
    <title>计算属性</title>
    <!-- 引入Vue -->
    <script type="text/javascript" src="../js/vue.js"></script>
  </head>
  <body>
    <!-- 准备好一个容器-->
    <div id="root">
     <h3>反转的message:{{reversedMessage}}</h3>
    </div>
  </body>
​
  <script type="text/javascript">
    Vue.config.productionTip = false //阻止 vue 在启动时生成生产提示。
​
    new Vue({
      el:'#root',
      data:{
        message: "hello world"
      },
      computed:{
        reversedMessage() {
          return this.message.split('').reverse().join('')
        }
      }
    })
  </script>
</html>

你可以打开浏览器的控制台,自行修改例子中的 vm。vm.reversedMessage 的值始终取决于 vm.message 的值。

你可以像绑定普通 property 一样在模板中绑定计算属性。Vue 知道 vm.reversedMessage 依赖于 vm.message,因此当 vm.message 发生改变时,所有依赖 vm.reversedMessage 的绑定也会更新

你可能已经注意到我们可以通过在表达式中调用方法来达到同样的效果:

<h3>反转的message:{{reversedMessage()}}</h3>
// 在组件中
methods: {
  reversedMessage() {
    return this.message.split('').reverse().join('')
  }
}

我们可以将同一函数定义为一个方法而不是一个计算属性。两种方式的最终结果确实是完全相同的。然而,不同的是计算属性是基于它们的响应式依赖进行缓存的。只在相关响应式依赖发生改变时它们才会重新求值。这就意味着只要 message 还没有发生改变,多次访问 reversedMessage 计算属性会立即返回之前的计算结果,而不必再次执行函数。

这也同样意味着下面的计算属性将不再更新,因为 Date.now() 不是响应式依赖:

computed: {
  now() {
    return Date.now()
  }
}

相比之下,每当触发重新渲染时,调用方法将总会再次执行函数。

我们为什么需要缓存?假设我们有一个性能开销比较大的计算属性 A,它需要遍历一个巨大的数组并做大量的计算。然后我们可能有其他的计算属性依赖于 A。如果没有缓存,我们将不可避免的多次执行 A 的 getter!如果你不希望有缓存,请用方法来替代。

上面展示都是计算属性的简写模式即只有getter方法进行读取属性,但是计算属性也是可以修改的,这时候就需要通过setter函数去响应修改了,下面来看看完整版的写法:通过姓和名组装得到计算属性:fullName:

<script type="text/javascript">
    Vue.config.productionTip = false //阻止 vue 在启动时生成生产提示。
​
    const vm = new Vue({
      el:'#root',
      data:{
        firstName:'张',
        lastName:'三',
      },
      computed:{
        fullName:{
          //get有什么作用?当有人读取fullName时,get就会被调用,且返回值就作为fullName的值
          //get什么时候调用?1.初次读取fullName时。2.所依赖的数据发生变化时。
          get(){
            console.log('get被调用了')
            // console.log(this) //此处的this是vm
            return this.firstName + '-' + this.lastName
          },
          //set什么时候调用? 当fullName被修改时。
          set(value){
            console.log('set',value)
            const arr = value.split('-')
            this.firstName = arr[0]
            this.lastName = arr[1]
          }
        }
      }
    })
  </script>

项目推荐:基于SpringBoot2.x、SpringCloud和SpringCloudAlibaba企业级系统架构底层框架封装,解决业务开发时常见的非功能性需求,防止重复造轮子,方便业务快速开发和企业技术栈框架统一管理。引入组件化的思想实现高内聚低耦合并且高度可配置化,做到可插拔。严格控制包依赖和统一版本管理,做到最少化依赖。注重代码规范和注释,非常适合个人学习和企业使用

Github地址https://github.com/plasticene/plasticene-boot-starter-parent

Gitee地址https://gitee.com/plasticene3/plasticene-boot-starter-parent

微信公众号Shepherd进阶笔记

交流探讨qun:Shepherd_126

3.监视属性Watch

定义方式: 监视属性是通过watch选项来定义的,它接受一个对象,对象的每个键都是要监视的数据属性的名称,对应的值是一个函数,用于在数据变化时执行自定义的操作。通过监视属性完成上面生成全名:

var vm = new Vue({
  el: '#demo',
  data: {
    firstName: 'Foo',
    lastName: 'Bar',
    fullName: 'Foo Bar'
  },
  watch: {
    firstName: function (val) {
      this.fullName = val + ' ' + this.lastName
    },
    lastName: function (val) {
      this.fullName = this.firstName + ' ' + val
    }
  }
})

来看看和计算属性对比,计算属性更加简洁。

computed:{
    reversedMessage() {
      return this.message.split('').reverse().join('')
    }
  }

虽然计算属性在大多数情况下更合适,但有时也需要一个自定义的侦听器。这就是为什么 Vue 通过 watch 选项提供了一个更通用的方法,来响应数据的变化。当需要在数据变化时执行异步或开销较大的操作时,这个方式是最有用的。还是上面生成全名的示例,只是现在要求输入姓氏之后1s中才能生成全名:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8" />
    <title>姓名案例_watch实现</title>
    <!-- 引入Vue -->
    <script type="text/javascript" src="../js/vue.js"></script>
  </head>
  <body>
    <!-- 
        computed和watch之间的区别:
            1.computed能完成的功能,watch都可以完成。
            2.watch能完成的功能,computed不一定能完成,例如:watch可以进行异步操作。
        两个重要的小原则:
              1.所被Vue管理的函数,最好写成普通函数,这样this的指向才是vm 或 组件实例对象。
              2.所有不被Vue所管理的函数(定时器的回调函数、ajax的回调函数等、Promise的回调函数),最好写成箭头函数,
                这样this的指向才是vm 或 组件实例对象。
    -->
    <!-- 准备好一个容器-->
    <div id="root">
      姓:<input type="text" v-model="firstName"> <br/><br/>
      名:<input type="text" v-model="lastName"> <br/><br/>
      全名:<span>{{fullName}}</span> <br/><br/>
    </div>
  </body>
​
  <script type="text/javascript">
    Vue.config.productionTip = false //阻止 vue 在启动时生成生产提示。
​
    const vm = new Vue({
      el:'#root',
      data:{
        firstName:'张',
        lastName:'三',
        fullName:'张-三'
      },
      watch:{
        firstName(val){
          // 定时器
          setTimeout(()=>{
            console.log(this)
            this.fullName = val + '-' + this.lastName
          },1000);
        },
        lastName(val){
          this.fullName = this.firstName + '-' + val
        }
      }
    })
  </script>
</html>

在这个示例中,使用 watch 选项允许我们执行异步操作,计算属性无法做到的。

深度监视

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8" />
    <title>天气案例_深度监视</title>
    <!-- 引入Vue -->
    <script type="text/javascript" src="../js/vue.js"></script>
  </head>
  <body>
    <!-- 
        深度监视:
            (1).Vue中的watch默认不监测对象内部值的改变(一层)。
            (2).配置deep:true可以监测对象内部值改变(多层)。
        备注:
            (1).Vue自身可以监测对象内部值的改变,但Vue提供的watch默认不可以!
            (2).使用watch时根据数据的具体结构,决定是否采用深度监视。
     -->
    <!-- 准备好一个容器-->
    <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/javascript">
    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:{
          // immediate:true, //初始化时让handler调用一下
          //handler什么时候调用?当isHot发生改变时。
          handler(newValue,oldValue){
            console.log('isHot被修改了',newValue,oldValue)
          }
        },
        //监视多级结构中某个属性的变化
        /* 'numbers.a':{
          handler(){
            console.log('a被改变了')
          }
        } */
        //监视多级结构中所有属性的变化
        numbers:{
          deep:true,
          handler(){
            console.log('numbers改变了')
          }
        }
      }
    })
​
  </script>
</html>

4.总结

计算属性和监视属性:

  1. 共同点:

    • 都用于响应数据变化。
    • 都是Vue实例的选项,可以在组件定义时使用。
    • 都可以监视数据变化,并在数据变化时执行自定义的操作。
  2. 区别:

    • 计算属性是基于依赖的缓存属性,只有在相关依赖发生改变时才会重新求值,而监视属性则没有缓存机制,每次数据变化都会触发相应的操作。
    • 计算属性通常用于处理需要根据其他数据动态计算得出的属性值,而监视属性用于执行异步或开销较大的操作,或者在数据变化时执行其他自定义的操作。

总之,计算属性和监视属性都是Vue提供的响应式数据处理机制,您可以根据具体的需求选择使用其中之一或者两者结合使用。