1️⃣ 计算属性
2️⃣ 基础例子
<template><div id="app"><div>{{ count }}</div><div @click="num1++">Add</div></div></template><script>export default {name: "App",data: function() {return {num1: 10,num2: 20,};},computed: {// 计算属性的简写形式count() {return this.num1 + this.num2;},},};</script>
2️⃣ 计算属性缓存 vs 方法
我们可以将同一函数定义为一个方法而不是一个计算属性。两种方式的最终结果确实是完全相同的。然而,不同的是计算属性是基于它们的响应式依赖进行缓存的。只在相关响应式依赖发生改变时它们才会重新求值。
当使用方法时,每一次页面重新渲染,对应的方法都会重新执行一次,但是利用计算属性做,就不会有这样的现象出现。计算属性有缓存而方法没有缓存。
我们为什么需要缓存?假设我们有一个性能开销比较大的计算属性 A,它需要遍历一个巨大的数组并做大量的计算。然后我们可能有其他的计算属性依赖于 A。如果没有缓存,我们将不可避免的多次执行 A 的 getter!如果你不希望有缓存,请用方法来替代。
2️⃣ 计算属性 vs 侦听属性
两者都可以观察和响应 Vue 实例上的数据的变动。
侦听器(watch)擅长处理的场景是:一个数据影响多个数据。计算属性(computed)擅长处理的场景是:多个数据影响一个数据。
在侦听器中可以执行异步,但是在计算属性中不可以。
2️⃣ 计算属性的 setter
计算属性除了写成一个函数之外,还可以写成一个对象,对象内有两个属性,getter & setter,这两个属性皆为函数,写法如下:
const vm = new Vue({el: "#app",computed: {setAget: {getter() {// 一些代码},setter() {// 一些代码},},},});
3️⃣ getter 读取
在前面,我们直接将计算属性写成了一个函数,这个函数即为 getter 函数。也就是说,计算属性默认只有 getter。
1. getter 的 this,被自动绑定为 Vue 实例。
当我们去获取某一个计算属性时,就会执行 get 函数。
get 被调用的时间
1. 当计算属性被初次调用时2. 当计算属性依赖的属性被修改时
<template><div id="app"><!-- 计算属性有缓存机制 多次调用 get 只会被调用一次 --><div>{{ count }}</div><div>{{ count }}</div><div>{{ count }}</div><div @click="num1++">Add</div></div></template><script>export default {name: "App",data() {return {num1: 10,num2: 20,};},computed: {count: {get() {console.log("get 被调用了");let num = this.num1 + this.num2;return num;},},},};</script>
3️⃣ setter 设置
set 函数在给计算属性重新赋值时会执行。set 的参数为被重新设置的值。
1. setter 的 this,被自动绑定为 Vue 实例。
set 的调用时间
1. 当计算属性本身被修改时 set 被调用,一般情况下计算属性是不修改的 set 的。
要注意,即使给计算属性赋了值,计算属性也不会改变,只有当依赖的响应式属性变化了,计算属性才会重新计算。
<template><div id="app"><div>{{ count }}</div><div @click="count = 50">Add</div></div></template><script>export default {name: "App",data() {return {num1: 10,num2: 20,};},computed: {count: {get() {console.log("get 被调用了");let num = this.num1 + this.num2;return num;},set(newV) {console.log("set 被调用了");this.num1 = newV;},},},};</script>
1️⃣ 侦听器
侦听属性,响应数据( data&computed )的变化,当数据变化时,会立刻执行对应函数
1. 侦听器函数,会接收两个参数,第一个参数为 newVal ( 被改变的数据 ),第二个参数为 oldVal ( 赋值新值之前的值 )。
<template><div id="app"><div>{{ num }}</div><h3 @click="change">change</h3></div></template><script>export default {name: "App",data() {return {num: 100,};},watch: {num(newVal, newOld) {console.log(newVal, newOld, "触发 watch");},},methods: {change() {if (this.num === 100) {this.num = 200;} else {this.num = 100;}},},};</script>
2️⃣ 字符串类型
值为方法名字,被侦听的数据改变时,会执行该方法。
<template><div id="app"><div>{{ num }}</div><h3 @click="change">change</h3></div></template><script>export default {name: "App",data() {return {num: 100,};},watch: {num: "watchMethod",},methods: {change() {if (this.num === 100) {this.num = 200;} else {this.num = 100;}},watchMethod(newVal, oldVal) {console.log(newVal, oldVal, "触发 watch 调用指定方法");},},};</script>
2️⃣ 对象类型
写成对象类型时,可以提供选项。
1. handler:handler 是被侦听的数据改变时执行的回调函数。handler 的值类型为函数/字符串,写成字符串时为一个方法的名字。2. deep:在默认情况下,侦听器侦听对象只侦听引用的变化,只有在给对象赋值时它才能被监听到。所以需要使用 deep 选项,让其可以发现对象内部值的变化,将 deep 的值设置为 true,那么无论该对象被嵌套的有多深,都会被侦听到。3. immediate**:**加上 immediate 选项后,回调将会在侦听开始之后立刻被调用。而不是等待侦听的数据更改后才会调用。
<template><div id="app"><div>{{ num.num1 }}</div><h3 @click="change">change</h3></div></template><script>export default {name: "App",data() {return {num: {num1: 100,},};},watch: {num: {handler(newVal, newOld) {console.log(newVal, newOld, "触发 watch");},deep: true,immediate: true,},},methods: {change() {if (this.num.num1 === 100) {this.num.num1 = 200;} else {this.num.num1 = 100;}},},};</script>
2️⃣ 数组类型
可以将多种不同值类型写在一个数组中。
<template><div id="app"><div>{{ num }}</div><h3 @click="change">change</h3></div></template><script>export default {name: "App",data() {return {num: 100,};},watch: {num: ["watchMethod",function() {console.log("触发 watch function 方法被调用了");},{handler(newVal, newOld) {console.log(newVal, newOld, "触发 watch");},deep: true,immediate: true,},],},methods: {change() {if (this.num === 100) {this.num = 200;} else {this.num = 100;}},watchMethod(newVal, newOld) {console.log(newVal, newOld, "触发 watch 调用指定方法");},},};</script>
2️⃣ 键类型
当 key 值类型为字符串时,可以实现监听对象当中的某一个属性,如:
<template><div id="app"><div>{{ num.num1 }}</div><h3 @click="change">change</h3></div></template><script>export default {name: "App",data() {return {num: {num1: 100,},};},watch: {"num.num1"(newVal, newOld) {console.log(newVal, newOld, "触发 watch");},},methods: {change() {if (this.num.num1 === 100) {this.num.num1 = 200;} else {this.num.num1 = 100;}},},};</script>
1️⃣ vm.$watch
Vue 实例将会在实例化时调用 $watch,遍历 watch 对象的每一个属性。我们也可以利用 vm.$watch 来实现侦听,用法与 watch 选项部分一致,略有不同。以下为使用方法。
2️⃣ 基本用法
<div><p>FullName: {{fullName}}</p><p>FirstName: <input type="text" v-model="firstName"></p></div>new Vue({el: '#root',data: {firstName: 'Dawei',lastName: 'Lou',fullName: ''},watch: {firstName(newName, oldName) {this.fullName = newName + ' ' + this.lastName;}}})
2️⃣ handler 方法和 immediate 属性:
上面的例子是值变化时候,watch才执行,我们想让值最初时候watch就执行就用到了**handler**和**immediate**属性
watch: {firstName: {handler(newName, oldName) {this.fullName = newName + ' ' + this.lastName;},// 代表在wacth里声明了firstName这个方法之后立即先去执行handler方法,如果设置了false,那么效果和上边例子一样immediate: true}}
2️⃣ deep 属性( 深度监听,常用于对象下面属性的改变 ):
<div><p>obj.a: {{obj.a}}</p><p>obj.a: <input type="text" v-model="obj.a"></p></div>new Vue({el: '#root',data: {obj: {a: 123}},watch: {obj: {handler(newName, oldName) {console.log('obj.a changed');},deep: true}}})
2️⃣ 侦听某个数据的变化
// 1. 三个参数,一参为被侦听的数据;二参为数据改变时执行的回调函数;三参可选,为设置的选项对象vm.$watch("msg",function () {},{deep: true,immediate: true,});// 2. 二个参数,一参为被侦听的数据;二参为选项对象,其中 handler 属性为必需,是数据改变时执行的回调函数,其他属性可选。vm.$watch("msg",{handler() {},deep: true,immediate: true,});
2️⃣ 侦听某个对象属性的变化
vm.$watch("msg.data",{handler() {},deep: true,immediate: true,});
2️⃣ watch 多个属性
<script>export default {data() {return {name: '',age: '',hobby: {aaa: '',bbb: '', // 不观察 bbbccc: ''}}},computed: {watchMore() {const { name, age } = thisconst { aaa, ccc } = this.hobbyreturn {name,age,aaa,ccc}}},watch: {watchMore(val) {console.log(val)}}}</script>
2️⃣ 当监听的数据的在初始不确定,由多个数据得到时,此时可以将第一个参数写成函数类型
<!-- ********** HTML ********** --><body><div0 id="app"><h1>{{msg1}}</h1><h1>{{msg2}}</h1><button @click="add" type="button">Add</button></div0></body>// ********** JS/VUE **********const vm = new Vue({el: '#app',data: {msg1: 10,msg2: 20},methods: {add() {this.msg1++this.msg2++},}})vm.$watch(function () {// 表达式`this.msg1 + this.msg2`每次得出一个不同的结果时该函数都会被调用// 这就像监听一个未被定义的计算属性return this.msg1 + this.msg2;},{handler(newVal, oldVal) {// 每次 this.msg1 + this.msg2 得出一个不同的值时就会调用该函数console.log(newVal, oldVal);},deep: true,immediate: true,});
2️⃣ 取消侦听函数
侦听器函数执行后,会返回一个取消侦听函数,用来停止触发回调:
<!-- HTML --><body><div id="app"><h1>{{msg1}}</h1><h1>{{msg2}}</h1><button @click="add" type="button">Add</button><button @click="cancel" type="button">Cancel</button></div></body>// JS/VUEconst vm = new Vue({el: '#app',data: {msg1: 10,msg2: 20},methods: {add() {this.msg1++this.msg2++},cancel() {unwatch()}}})const unwatch = vm.$watch(function () {return this.msg1 + this.msg2;},{handler(newVal, oldVal) {console.log(newVal, oldVal);},deep: true,immediate: true,});
使用 unwatch 时,需要注意的是,在带有 immediate 选项时,不能在第一次回调时取消侦听数据。
// JS/VUEconst unwatch = vm.$watch('msg',function () {unwatch(); // 此时会报错},{immediate: true}})
如果仍然希望在回调内部用一个取消侦听的函数,那么可以先检查该函数的可用性:
// JS/VUEconst vm = new Vue({el: '#app',data: {msg: 100,},methods: {add() {this.msg++},}})var unwatch = vm.$watch('msg',function () {if (unwatch) {unwatch();}},{immediate: true});
