# 一、计算属性

  计算属性是指通过一系列运算之后,最终得到的一个特殊的属性值。

# 1、代码体验

<div id="app">
  <input type="text" v-model="msg" />
  <p>{{ reverseMsg }}</p>
</div>

<script>
  var vm = new Vue({
    el: '#app',
    data: {
      msg: ''
    },
    computed: {
      // 计算属性的 getter
      reverseMsg: function () {
        // `this` 指向 vm 实例
        return this.msg.split('').reverse().join('')
      }
    }
  })
</script>

# 2、与方法的区别 ✨

与使用方法的区别

  调用方法的话,无论什么情况都会执行函数。

<script>
  var vm = new Vue({
    methods: {
      reverseMsg: function () {
        console.log('执行了一次')
        return this.msg.split('').reverse().join('')
      }
    }
  })
</script>

  而计算属性是可以基于它们的响应式依赖进行缓存的,即如果 msg 没有发生改变,使用 reverseMsg时返回的是之前的计算结果,而没有再次执行函数。

<div id="app">
  <button @click="updateTime">更新</button>
  <p>{{ now }}</p>
</div>

<script>
  var vm = new Vue({
    el: '#app',
    data: {
      now: Date.now()
    },
    // Date.now() 不是响应式依赖,计算属性将不再更新
    methods: {
      // 这时得使用方法
      updateTime() {
        this.now = Date.now()
      }
    }
  })
</script>

# 3、与监听器的区别 ✨

  有时候,我们使用计算属性要比使用监听器简洁的多:

原因

  通过下面的案例,我们发现:watch 监听的是已经定义的数据,而 computed 本身就可以作为数据被调用。

<div id="app">
  <p>{{ fullName }}</p>
</div>

<script>
  var vm = new Vue({
    el: '#app',
    data: {
      firstName: 'Foo',
      lastName: 'Bar'
      // fullName: 'Foo Bar'
    },
    computed: {
      fullName: function () {
        return this.firstName + ' ' + this.lastName
      }
    }
    // watch: {
    //   firstName: function (val) {
    //     this.fullName = val + ' ' + this.lastName
    //   },
    //   lastName: function (val) {
    //     this.fullName = this.firstName + ' ' + val
    //   }
    // }
  })
</script>
RGB 颜色监听案例
<div id="app">
  <div>
    <span>R:</span>
    <input type="text" v-model.number="r" />
  </div>
  <div>
    <span>G:</span>
    <input type="text" v-model.number="g" />
  </div>
  <div>
    <span>B:</span>
    <input type="text" v-model.number="b" />
  </div>

  <hr />
  <!-- 使用计算属性 -->
  <div class="box" :style="{backgroundColor: rgb}"></div>
</div>

<script>
  new Vue({
    el: '#app',
    data: {
      r: 0,
      g: 0,
      b: 0
    },
    computed: {
      rgb() {
        return `rgb(${this.r}, ${this.g}, ${this.b})`
      }
    }
  })
</script>

# 4、getter / setter

注意

原理:底层借助了 Object.defineproperty 方法提供的 getter 和 setter

data 和 计算属性 ✍

  通过在控制台打印,我们可以观察它们的位置:

vm
vm.firstName
vm.firstName

vm.fullName # Foo Bar
vm._data.firstName
vm._data.firstName

vm._data.fullName # undefined

  书写方式也很类似:

<script>
  var vm = new Vue({
    data: {
      msg: ''
    }
    computed: {
      // 简写(只有get)
      reverseMsg: function() {}
      // reverseMsg() {}

      // 完整写法
      reverseMsg: {
        get() {},
        set(newValue) {}
      }
    }
  })
</script>

  前面我们使用计算属性的写法是简写形式(只读不改),下面我们加入 getter、setter 观察一些细节:

<div id="app">
  <!-- 调用get -->
  <input type="text" v-model="msg" />
  <p>{{ reverseMsg }}</p>
  <p>{{ reverseMsg }}</p>
  <p>{{ reverseMsg }}</p>

  <!-- 调用set -->
  <button @click="reverseMsg = 'neR'">修改计算属性值</button>
</div>

<script>
  var vm = new Vue({
    el: '#app',
    data: {
      msg: 'lencamo'
    },
    computed: {
      reverseMsg: {
        // 1、get调用时机
        //   初次读取reverseMsg时
        //   响应式依赖的数据msg变化时
        get() {
          // 细节:get()方法只调用了👀一次
          console.log('get函数被调用')
          return this.msg.split('').reverse().join('')
        },

        // 2、set调用时机
        //   reverseMsg被修改时
        set(newValue) {
          console.log('计算属性被修改')
          // 作用:将修改更新到data中响应式依赖
          this.msg = newValue.split('').reverse().join('')
        }
      }
    }
  })
</script>

# 二、监听器

提示

  我们用 computed 能实现的功能,watch 都能实现 🤔。

监听器监听的是数据的变化,这就意味着 watch 不仅可以监听 data 数据,还可以监听计算属性。

# 1、代码体验

<div id="app">
  <input type="text" v-model="username" />
</div>

<script src="./lib/vue-2.6.14.js"></script>
<script>
  const vm = new Vue({
    el: '#app',
    data: {
      username: 'lencamo'
    },
    watch: {
      // 简写(注意参数的顺序🚩)
      username(newVal, oldVal) {
        console.log('值' + oldVal + '发生了改变!!\n' + '新值为:' + newVal)
      }
      // username: function (newVal, oldVal) {
      //   console.log('值' + oldVal + '发生了改变!!\n' + '新值为:' + newVal)
      // },

      // 完整的写法(和前面的computed类似)
      // username: {
      //   handler(newVal, oldVal) {
      //     console.log('值' + oldVal + '发生了改变!!\n' + '新值为:' + newVal)
      //   }
      // }
    }
  })
</script>

  进一步的,我们还可以这样写:

vm.$watch('username', {
  handler(newVal, oldVal) {
    console.log('值' + oldVal + '发生了改变!!\n' + '新值为:' + newVal)
  }
})

思考?

监听是的变化频率:防抖节流

# 2、应用场景 ✨

  当需要在数据变化时执行异步或开销较大的操作时,采用 watch 是最有用的

  • 定时器
const vm = new Vue({
  watch: {
    firstName(newVal) {
      setTimeout(() => {
        console.log(this) // vm

        this.fullName = val + ' ' + this.lastName
      }, 1000)
    }
  }
})

this 指向

  为了让 this 指向 vm 或者组件的实例对象,有如下建议:

  • 被 vue 管理的函数,写成普通函数
  • 不被 vue 管理的函数,写成箭头函数
  • 用户名占用
<script>
  const vm = new Vue({
    el: '#app',
    data: {
      username: 'admin'
    },
    watch: {
      async username(newVal, oldVal) {
        if (newVal === '') return
        // 使用axios发起ajax请求,判断username是否被占用
        const { data: res } = await axios({
          method: 'GET',
          url: 'https://www.escook.cn/api/finduser/' + newVal
        })
        console.log(res)
      }
    }
  })
</script>

# 3、immediate 选项

  默认情况下,组件在初次加载完毕时不会调用 watch 监听器(即上面的案例中 首次是不会监听用户名是否被占用的)。

  而在使用计算属性则不同,计算属性本身就会在:初次读取 computed 计算属性时就被调用

immediate 选项的存在,间接证明了使用计算属性的优越性。

<div id="app">
  <input type="text" v-model="keyWord" /><br />
  <ul>
    <li v-for="item in listData" :key="item.id">{{item.music}}</li>
  </ul>
</div>

<script>
  new Vue({
    el: '#app',
    data: {
      keyWord: '',
      data: [
        { id: 1, music: '十年--陈奕迅' },
        { id: 2, music: '分手快乐--梁静茹' },
        { id: 3, music: '十一年--邱永传' },
        { id: 4, music: '阴天快乐--陈奕迅' }
      ]
      // listData: [] // 专门定义供watch监听使用
    },
    // 计算属性方式
    computed: {
      listData() {
        return this.data.filter((item) => {
          return item.music.includes(this.keyWord)
        })
      }
    }

    // 监听器方式
    // watch: {
    //   keyWord: {
    //     handler(newVal) {
    //       this.listData = this.data.filter((item) => {
    //         return item.music.includes(newVal)
    //       })
    //     },

    //     immediate: true // // 初次加载时就调用watch监听器
    //   }
    // }
  })
</script>

# 4、deep 选项

  当 watch 监听的是一个对象时,当对象的属性值发生了变化,是无法被监听到的。

// 监听对象属性值变化
const vm = new Vue({
  el: '#app',
  data: {
    brand: {
      id: 1,
      brandName: 'lisi',
      status: true,
      companyName: '京东'
    }
  },
  watch: {
    brand: {
      handler: async function (newVal) {
        // ……
        console.log(newVal.brandName)
        console.log(newVal.status)
      },

      deep: true
    }
  }
})

  当然如果不使用 deep 选项,是完全可以直接监听单个对象属性值变化的。

// 监听单个对象属性值变化
watch: {
  'brand.brandName': {
    handler: async function (newVal) {
      ……
      console.log(newVal)
    }
  }
}

# 5、$watch

const app = Vue.createApp({
  created() {
    // ajax/fetch/axios
    console.log('created')

    this.$watch(
      'message',
      (newValue, oldValue) => {
        //
      },
      { deep: true, imediate: true }
    )
  }
})

# 三、filters 过滤器 *

  上面进行假删除时使用了 filter,但要注意的是 vue3.0 已经弃用了 filters。Vue.js 允许你自定义过滤器,它常用的场景(对文本格式化):

  • 双花括号插值中
  • v-bind 表达式中
<div id="app1">
  <!-- 在双花括号中 -->
  {{ message | capitalize }}

  <!-- 在 `v-bind` 中 -->
  <div v-bind:id="idtext | maxLength(12)">div盒子id值长度测试</div>
</div>

<div id="app2">
  <!-- 在 `v-bind` 中 -->
  <div v-bind:id="idtext | maxLength(12)">div盒子id值长度测试</div>
</div>

<script src="./lib/vue-2.6.14.js"></script>
<script>
  // 1、全局过滤器🤔
  // 注意观察参数
  Vue.filter('maxLength', function (value, len) {
    if (value.length <= len) {
      return value
    }
    return value.substr(0, len)
  })

  const vm1 = new Vue({
    // DOM Listeners处理
    el: '#app1',
    // Data Bindings处理
    data: {
      message: 'zhangsan',
      idtext: 'head-for-table-first-cell'
    },
    // 2、本地过滤器
    filters: {
      capitalize: function (value) {
        if (!value) return ''
        value = value.toString()
        return value.charAt(0).toUpperCase() + value.slice(1)
      }
    }
  })

  const vm2 = new Vue({
    // DOM Listeners处理
    el: '#app2',
    // Data Bindings处理
    data: {
      idtext: 'head-for-table-first-cell'
    }
  })
</script>
更新于 : 8/7/2024, 2:16:31 PM