TOC
computed属性
在Vue实例中,还有一个computed属性,该属性为Vue框架提供的计算属性功能,所谓计算属性,就是某些属性需要通过计算才能得到最后的值,这样的数据Vue官方推荐使用计算属性来实现,官方对计算属性也没有给出太多详细对解释,而是说,对于任何包含响应式数据对复杂逻辑,都应该使用计算属性,所谓响应式数据,就说data函数返回的对象;
在Vue模版中可以直接通过插值语法显示data函数返回对象的数据,但在某些情况下,我们可能需要对这个数据做进一步对处理,以往都是在插值语法内进行,但是Vue框架设计插值语法的初衷是用于简单运算的,对于复杂的运算逻辑写在模板中会让模板过重且难以维护;
当然,这个时候我们可能会想到使用methods处理函数来将这些需要处理的数据进行处理,然后将处理后的结果返回,然后在插值语法里面传递一个函数,这样也是可行的,但是这有一个问题,就是一旦我们在当前模版或者实例中多次引用到这个methods方法时,在Vue渲染模版的那一刻会多次执行这个方法,从而导致方法函数多次执行,如果这个处理的过程很复杂,且耗时较长,那么就带来了极大的性能浪费;
那么对于这种场景,computed计算属性其实更加合适,computed计算属性有一个特性就是它自带缓存功能,一旦遇到这种场景,只要响应式数据没有发生变化,就不会重新执行,执行一次之后,会将这个计算的值缓存起来,下一次直接从缓存中获取这个值,极大的提升了渲染性能;
此外,computed计算属性和methods还有一个不同之处,就是在模版中,想要使用computed计算属性方法,无需使用调用函数的方式,直接插值语法的方式即可,将计算属性内的方法标识符写在"{{}}"内;
那么对于this来讲,computed计算属性内的方法函数中使用this,和methods处理函数中使用this是一致的,他们都代表data函数返回的对象;
<script src="https://cdn.bootcdn.net/ajax/libs/vue/3.2.40/vue.global.js"></script>
<div id="box">
<p>method:{{ methodFullName() }}</p>
<p>method:{{ methodFullName() }}</p>
<p>computed:{{ computedFullName }}</p>
<p>computed:{{ computedFullName }}</p>
<button @click="chengge">修改属性</button>
</div>
<script>
Vue.createApp({
data() {
return {
firstName: "Cai",
lastName: "ChangEn"
}
},
methods: {
methodFullName() {
console.log('method')
return this.firstName + this.lastName
},
chengge() {
this.lastName = "nidaye"
}
},
computed: {
computedFullName() {
console.log('computed')
return this.firstName + this.lastName
}
}
}).mount("#box")
</script>
如上结果,如果在模版引擎中两次引用,使用methods处理函数实现的,会调用两次对应的methods方法,而使用computed计算属性实现的,只需要调用一次computed方法,后面一次会直接从缓存中取数据;
计算属性本质
其实计算属性本质就是一个数据描述符,上述的写法只是一个语法糖,其实它本质就是一个对象,对象内部存在set和get方法,通过这两个方法来捕获这个计算属性的变化,同时,这两个方法都是函数,set方法监听计算属性的变化,它默认接收一个参数,即传入的新值,如下;
<script src="https://cdn.bootcdn.net/ajax/libs/vue/3.2.40/vue.global.js"></script>
<div id="box">
<p>computed:{{ computedFullName }}</p>
<p>computed:{{ computedFullName }}</p>
<button @click="chengge">修改属性</button>
</div>
<script>
Vue.createApp({
data() {
return {
firstName: "Cai",
lastName: "ChangEn"
}
},
methods: {
chengge() {
this.computedFullName = "Cai DaYe"
}
},
computed: {
computedFullName: {
set: function (newValue) {
console.log("setattr")
const [firstName, lastName] = newValue.split(' ') // 解构赋值
this.firstName = firstName
this.lastName = lastName
},
get: function () {
console.log("getattr")
return this.firstName + this.lastName
}
}
}
}).mount("#box")
</script>
watch属性
在Vue实例中,有一个watch的属性,一般都称它之为侦听器,在开发的过程中,data函数返回的对象,可以使用插值语法来绑定在模版中,当数据发生变化时,template会自动将最新的数据渲染到页面上,但是在某些情况下,我们系统在代码逻辑监听某个数据的变化,这个时候就可以用到watch侦听器来完成;
它是一个对象,它和methods及computed是一样的,内部都是一些函数方法,但是watch却有点不一样,它的函数名称,是响应式数据的标识符,即data函数返回的对象内数据的标识符,此外,当数据发生变化时,默认会接收到两个参数,第一个参数为属性改变后的值,第二个参数为改变前的值,如下;
<script src="https://cdn.bootcdn.net/ajax/libs/vue/3.2.40/vue.global.js"></script>
<div id="box">
<p>{{name}}</p>
<button @click="chengge">修改属性</button>
</div>
<script>
Vue.createApp({
data() {
return { // 响应式数据
name: "cce"
}
},
methods: {
chengge() {
this.name = 'caichangen'
}
},
watch: {
name(newValue, oldValue) { // 函数的标识符需要和响应式数据属性名一致
console.log(`改变后的值为${newValue},改变前的值为${oldValue}`)
}
}
}).mount("#box")
</script>
深度监听
利用函数的方式来编写我们的侦听起方法,无法达到深度监听的效果,就比如,如果需要修改的数据是一个对象,但是并不是直接修改整个对象,而是修改对象内的属性,在这种场景下,使用函数的方式是无法监听到的,如下;
<script src="https://cdn.bootcdn.net/ajax/libs/vue/3.2.40/vue.global.js"></script>
<div id="box">
<p>My name is {{userInfo.name}} and age is {{userInfo.age}}</p>
<button @click="chengge">修改年龄</button>
</div>
<script>
Vue.createApp({
data() {
return {
userInfo: {name: "cce", age: 18}
}
},
methods: {
chengge() {
this.userInfo.age = 26
}
},
watch: {
userInfo(newValue, oldValue) {
console.log(`改变后的值为${newValue},改变前的值为${oldValue}`)
}
}
}).mount("#box")
</script>
如上结果,我们直接修改对象内的属性值,而不是直接修改对象本身,这样使用函数的方式是无法监听到的,那么这个时候,就需要对其进行深度监听,那么对于深度监听来讲,就需要使用对象的方式来定义我们的侦听器函数;
使用对象的形式来定义侦听器时,对象名称,需要和响应式数据的标识符一致(data函数返回对象的属性名),需要在这个对象内部加入一个handler方法,该handler就时上述的侦听器函数的形式,接收两个参数,第一个为修改前的数据,第二个为修改后的数据,当侦听到这个侦听器侦听的数据被修改时,会触发这个函数的执行;
此外,还可以在这个对象内部,加入一个deep的属性,设置该属性为true时,表示启用深度监听,如下示例;
<script src="https://cdn.bootcdn.net/ajax/libs/vue/3.2.40/vue.global.js"></script>
<div id="box">
<p>My name is {{userInfo.name}} and age is {{userInfo.age}}</p>
<button @click="chengge">修改年龄</button>
</div>
<script>
Vue.createApp({
data() {
return {
userInfo: {name: "cce", age: 18}
}
},
methods: {
chengge() {
this.userInfo.age = 26
}
},
watch: {
userInfo: {
handler(newValue, oldValue) {
console.log(newValue, oldValue)
},
deep: true
}
}
}).mount("#box")
</script>
可以看到上述的结果,虽然确实监听到了对象内属性的变化了,但是拿到的oldValue参数却是修改后的值,这肯定是有问题的,这是因为数据的内存地址,并未发生改变,newValue 和 oldValue 由于是同一个引用,所以属性值是一样的;
那么想要解决这个问题,可以精确到对象的属性名进行深度侦听来解决此问题,如下;
<script src="https://cdn.bootcdn.net/ajax/libs/vue/3.2.40/vue.global.js"></script>
<div id="box">
<p>My name is {{userInfo.name}} and age is {{userInfo.age}}</p>
<button @click="chengge">修改年龄</button>
</div>
<script>
Vue.createApp({
data() {
return {
userInfo: {name: "cce", age: 18}
}
},
methods: {
chengge() {
this.userInfo.age = 26
}
},
watch: {
'userInfo.age': {
handler(newValue, oldValue) {
console.log(newValue, oldValue)
},
deep: true
}
}
}).mount("#box")
</script>
此外,如果我们希望在页面打开的那一刻,执行一次侦听器函数,可以加入一个immediate属性,当该属性为true时,页面打开那一刻,就会立即执行一次侦听器函数,即handler函数;
<script src="https://cdn.bootcdn.net/ajax/libs/vue/3.2.40/vue.global.js"></script>
<div id="box">
<p>My name is {{userInfo.name}} and age is {{userInfo.age}}</p>
</div>
<script>
Vue.createApp({
data() {
return {
userInfo: {name: "cce", age: 18}
}
},
watch: {
userInfo: {
handler(newValue, oldValue) {
console.log(newValue, oldValue)
},
deep: true,
immediate: true
}
}
}).mount("#box")
</script>