VU2 学习笔记4 计算属性、监视属性
计算属性computed
先写一个小demo,页面上有输入姓和名的地方,先用{{}}、methods实现功能:
<body><div id="root">firstName:<input type="text" v-model="firstName"><br />lastName:<input type="text" v-model="lastName"><br />totName:<span>{{firstName}}{{lastName}}</span></div><script type="text/javascript" src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script><script>let vm = new Vue({el: '#root',data() {return {firstName: 'c',lastName: 'at',}},})</script>
</body>
如果现在需要实现某个功能,不管firstName和lastName输入几位,都只取前三位、首字母大写。这时候如果都写在{{}}里,相关的逻辑就会过多(都写在{{}}里当然也不会报错,只是可读性比较低)。
一种改进方式,是可以把逻辑写在methods里。
<body><div id="root">firstName:<input type="text" v-model="firstName"><br />lastName:<input type="text" v-model="lastName"><br />totName:<span>{{getTotName()}}</span></div><script type="text/javascript" src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script><script>let vm = new Vue({el: '#root',data() {return {firstName: 'caef',lastName: 'atef',}},methods:{getTotName(){let firstData = this.firstName.slice(0,3);firstData = firstData[0]?.toUpperCase() + firstData?.slice(1);let lastData = this.lastName.slice(0,3);lastData = lastData[0]?.toUpperCase() + lastData?.slice(1);return firstData+lastData;}}})</script>
</body>
注意:如果在{{}}里使用vm的methods,需要给函数加括号,否则显示的就是函数本身。
当data数据发生变化时,模版会重新进行解析。方法也会重新进行调用。所以data的变化会反馈在{{}}中。
用计算属性实现:
属性(property):对Vue来说,data配置项里的内容就是属性。
计算属性:通过已经有的属性或方法,进行一系列加工和计算,生成的全新的属性。
需要注意:Vue中data中的数据和计算数据是分开的,使用计算属性计算出的属性,不能在data中重复设置。
计算属性用computed:{}配置。
computed:{
}
computed内部,每个计算属性的配置有多种形式。可以通过属性名:配置对象的格式配置,配置对象内部需要添加get方法,vue会把get方法的返回值赋给计算属性,并把计算属性添加到实例上:
computed:{
属性名:{
get(){...}
}
}
在计算属性内部,如果设置的是普遍函数,this指向vue实例。如果是箭头函数,this指向window。
<body><div id="root">firstName:<input type="text" v-model="firstName"><br />lastName:<input type="text" v-model="lastName"><br />totName:<span>{{getTotName}}</span></div><script type="text/javascript" src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script><script>let vm = new Vue({el: '#root',data() {return {firstName: 'caef',lastName: 'atef',}},computed:{getTotName:{get(){let firstData = this.firstName.slice(0,3);firstData = firstData[0]?.toUpperCase() + firstData?.slice(1);let lastData = this.lastName.slice(0,3);lastData = lastData[0]?.toUpperCase() + lastData?.slice(1);return firstData+lastData;}}}})</script>
</body>
对象式配置中,也可以配置set,set不是必须写的,如果计算属性不需要修改,则不需要写set。set在计算属性被修改时调用:
计算属性的另一种形式是函数式:
computed:{
属性名(){
}
}
对象式是计算属性的完整写法,但是一般情况下,计算属性通常是不被修改的,因此计算属性有一种简写形式:函数形式。需要注意,函数形式默认写的是get,不能写set。
getTotName: function(){let firstData = this.firstName.slice(0, 3);firstData = firstData[0]?.toUpperCase() + firstData?.slice(1);let lastData = this.lastName.slice(0, 3);lastData = lastData[0]?.toUpperCase() + lastData?.slice(1);return firstData + lastData;}
可以把配置对象直接写成函数,这个函数会被理解为get。这种形式还可以进一步简写:
computed: {getTotName() {let firstData = this.firstName.slice(0, 3);firstData = firstData[0]?.toUpperCase() + firstData?.slice(1);let lastData = this.lastName.slice(0, 3);lastData = lastData[0]?.toUpperCase() + lastData?.slice(1);return firstData + lastData;}}
计算属性配置后,也会出现在Vue实例上。
但计算属性不会被添加到_data中,计算属性是计算完成时,直接添加到实例上。
缓存机制:虽然计算属性是以get的形式读取,但模版解析时,会把属性值缓存下来,后面再调用属性,不会再次get,会使用缓存值。
计算属性什么时候重新计算:一种情况是初次读取属性时,另一种情况是计算属性所依赖的数据发生变化时。计算属性重新计算时,会更新缓存。
与methods相比,methods没有缓存机制,methods被调用几次,就执行几次。
控制台就打印一次getTotName :
computed底层是借助object.defineproperty实现的。
监视属性watch
watch可以监听某个属性的改变,当属性值改变时,就触发监听事件。
watch的配置形式是watch:{}。
对于想监视的属性,也有两种配置形式,一种是以配置对象的形式: 被监视的属性:{}
watch:{
被监视的属性:{
}
}
用配置对象的形式时,可以配置的一些配置项:
handler函数:当被监视的属性发生改变时,handler函数就会被调用。
<body><div id="root"><button @click="changeData">change</button></div><script type="text/javascript" src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script><script>let vm = new Vue({el: '#root',data() {return {bol: true,}},watch:{bol:{handler(){console.log('change');}}},methods:{changeData(){this.bol = !this.bol;}}})</script>
</body>
当点击按钮时,修改bol的值,触发bol上设置的监听事件,控制台输出change。
handler函数提供两个参数,是 发生变化后的属性 和 发生变化前的属性:(两个变量的名字可以写成任意的,但为了可读性,一般都写newValue,oldValue相关的)。
换成其他变量名也可以:
immediate:布尔值,默认是false,配置初始化时,监听事件直接被执行一次,不需要属性改变就能被执行一次。
没点按钮之前,监听事件就被执行一次。
深度监测deep:
对于一些嵌套的数据结构,watch监听事件可能并不生效。因为Vue只监视value值整个,也就是如果value是对象,只要value的地址不变化,就不会触发监听,Vue不会关心value对象地址内部的变量有没有变化。
每次参数变化时,彻底替换整个value,就会触发监听事件,但这种方法过于笨重。
配置deep,可以设置默认检测value指向地址内部变量的变化,deep默认值为false,表示不深度监测。
(vue实例是能够监听多层级数据结构变化的,但watch事件默认是检测多层级)
<body><div id="root"><button @click="changeData">change</button></div><script type="text/javascript" src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script><script>let vm = new Vue({el: '#root',data() {return {bol: {cat:1,dog:1,},}},watch:{bol:{//immediate:true,handler(a,b){console.log(`${a} ---- ${b}`);}}},methods: {changeData() {this.bol.cat++;console.log(this.bol.cat);}}})</script>
</body>
点击页面上的按钮时,修改bol.cat的值,点击按钮,cat值会增加,但不会触发bol上的watch事件。
对于对象中的key,直接写bol.cat会报错,因为bol.cat是不合法的key,对象中的key正常是一个字符串,平时直接写变量名,是一种简写形式,而bol.cat不是合法的简写形式,这里可以修改成字符串。
不合法:
合法:
但是对于这种形式,如果变量中有多个嵌套的变量都需要被监测,则需要写多个监听事件,如果变量数量成百上千,每个都写完全是不可能的,更通用的方法是配置深度监测:
watch也可以用vue实例调用:
以vm.$watch(要监听的属性(字符串格式),配置对象)。
监视不存在的属性时,虽然监视没有意义,但不会报错:
vm.$watch('cat', {immediate: true,handler(a, b) {console.log(`${a} ---- ${b}`);}})
当监视页面上不存在的属性时,控制台不会报错,甚至还可以执行一次:
简写形式:
如果不需要immediate,也不需要deep,就可以用简写形式,
watch: {bol(newValue, oldValue) {console.log(`${a} ---- ${b}`);}},
vm.$watch形式也可以简写:
vm.$watch('bol',function(newValue, oldValue){console.log(`${a} ---- ${b}`);})
如果需要设置immediate或者deep,就不能使用上述简写形式。
watch与computed:
虽然很多功能,好像既可以用watch,也可以用computed来计算,但两者之间还是存在差异的。
对于某些功能,确实是用watch才能实现,用watch可以使用异步任务,因为watch并不需要返回值。对于computed,在使用异步任务时,部分情况下无法返回正确的数值。
对于某些功能,如果用watch和computed都能进行实现,通常情况下用computed更简单。
配置对象里的普通函数和箭头函数:
在配置配置对象时,所有被vue管理的函数,一般应写成普通函数,this指向vm实例或组件实例。对于非vue管理的函数,一般应写成箭头函数,让this指向实例。
以setTimeout为例,setTimeout是非vue管理的函数,到了定时的时间,不是vue调的回调函数,而是js引擎调的。在需要使用定时器的情况下,如果内部的回调函数是普通函数,this不会指向实例,一般会指向window。当回调函数返回一个值时,返回值不会被交到setTimeout外面。也不能用回调函数指定vue方法的返回值。