欢迎大家订阅【Vue2+Vue3】入门到实践 专栏,开启你的 Vue 学习之旅!
前言
Vue.js 提供了丰富的功能,帮助开发者高效地构建用户界面。本篇文章详细讲解了其计算属性的基本语法、应用场景以及性能差异。
一、基础语法
①定义
计算属性是基于其他数据属性的值计算而来的衍生数据。
它通过一个函数自动重新计算,能够简化模板中的逻辑,提高代码的可读性和可维护性。计算属性不仅仅是封装了一段代码,它还能根据依赖的数据变化进行反应式更新。
②默认写法
computed:{
计算属性名(){
一段代码逻辑(计算逻辑)
return 结果
}
}
① 声明在 computed 配置项中,一个计算属性对应一个函数
② 使用起来和普通属性一样使用 {{ 计算属性名 }}
计算属性 → 可以将一段 求值的代码 进行封装
【示例】
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
table {
border: 1px solid #000;
text-align: center;
width: 240px;
}
th,td {
border: 1px solid #000;
}
h3 {
position: relative;
}
</style>
</head>
<body>
<div id="app">
<h3>礼物清单</h3>
<table>
<tr>
<th>名字</th>
<th>数量</th>
</tr>
<tr v-for="(item, index) in list" :key="item.id">
<td>{{ item.name }}</td>
<td>{{ item.num }}个</td>
</tr>
</table>
<!-- 目标:统计求和,求得礼物总数 -->
<p>礼物总数:{{totalCount}} 个</p>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
const app = new Vue({
el: '#app',
data: {
// 现有的数据
list: [
{ id: 1, name: '篮球', num: 1 },
{ id: 2, name: '玩具', num: 2 },
{ id: 3, name: '铅笔', num: 5 },
]
},
computed:{
totalCount(){
// 基于现有的数据,编写求值逻辑
// 计算属性函数内部可直接通过 this 访问到 app 实例
//需求:对 this.list 数值里面的 num 进行求和→reduce
let total = this.list.reduce((sum,item)=>sum+item.num,0)
return total
}
}
})
</script>
</body>
</html>
运行结果:
【分析】
let total = this.list.reduce((sum, item) => sum + item.num, 0);
①reduce()
:数组的一个方法,它用于对数组中的每个元素执行一个函数,将其汇总为一个单一的结果。reduce
方法接受两个参数:
- 参数1:一个回调函数。
- 参数2:一个初始值,通常情况下是 0。
② (sum, item) => sum + item.num
:一个箭头函数,作为 reduce
的第一个参数。我们可以把该函数拆分成两个部分:
sum
:表示累加器,它是上一次调用函数返回的值。item
:表示当前处理的数组元素。- 函数内部的逻辑
sum + item.num
:将当前的累加器sum
和当前对象的num
属性相加,并返回这个结果。这个返回值会成为下一次调用的sum
值。 0
:表示累加的初始值是 0。第一次调用时,sum
的初始值为 0。
该代码遍历 this.list
数组中的每一个项(item
),对于每一个 item
,提取它的 num
值,并不断地将这些 num
值相加到 sum
中,最终,返回所有 num
值的总和并赋给 total
变量。
执行过程如下:
第一步:sum = 0, item = { id: 1, name: '篮球', num: 1 } → 0 + 1 = 1
第二步:sum = 1, item = { id: 2, name: '玩具', num: 2 } → 1 + 2 = 3
第三步:sum = 3, item = { id: 3, name: '铅笔', num: 5 } → 3 + 5 = 8
二、计算属性vs方法
特性 | 计算属性 (computed) | 方法 (methods) |
---|---|---|
定义 | 基于现有的数据,计算出新属性 | 提供一个可调用的方法,以处理业务逻辑 |
作用 | 封装一段数据处理逻辑,计算并返回结果 | 定义这一实例的行为,用于处理业务逻辑 |
声明位置 | computed 配置项中 | methods 配置项中 |
访问方式 | 作为属性,直接使用{{ 计算属性名 }} 或 this.计算属性名 | 作为方法,需要调用{{ 方法名() }} 或 this.方法名() |
自动依赖追踪 | 是(只有在依赖的数据变化时才会重新计算) | 否(每次调用都会执行函数,无论依赖的数据是否变化) |
缓存 | 计算结果会被缓存,性能更高 | 不会缓存,每次调用都会重新计算 |
返回值 | 直接返回计算结果 | 直接返回计算结果 |
特定场景适用性 | 高频更新、需要对依赖数据变化反应的情况 | 适合需要执行不定逻辑的情况 |
可写性 | 默认是只读的,如果需要修改需完整定义 getter 和 setter | 可以在方法中进行参数传递和逻辑处理 |
缓存特性:
计算属性会缓存计算结果,后续使用时会直接读取缓存。当其依赖的响应式数据发生变化时,计算属性会进行重新计算并更新缓存。即使模板频繁渲染,计算属性也能够避免重复计算,从而大大提升性能。
【示例】
①computed计算属性
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
table {
border: 1px solid #000;
text-align: center;
width: 300px;
}
th,td {
border: 1px solid #000;
}
h3 {
position: relative;
}
span {
position: absolute;
left: 145px;
top: -4px;
width: 16px;
height: 16px;
color: white;
font-size: 12px;
text-align: center;
border-radius: 50%;
background-color: #e63f32;
}
</style>
</head>
<body>
<div id="app">
<h3>礼物清单🛒<span>{{ totalCount }}</span></h3>
<h3>礼物清单🛒<span>{{ totalCount }}</span></h3>
<h3>礼物清单🛒<span>{{ totalCount }}</span></h3>
<table>
<tr>
<th>名字</th>
<th>数量</th>
</tr>
<tr v-for="(item, index) in list" :key="item.id">
<td>{{ item.name }}</td>
<td>{{ item.num }}个</td>
</tr>
</table>
<p>礼物总数:{{ totalCount }} 个</p>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
const app = new Vue({
el: '#app',
data: {
// 现有的数据
list: [
{ id: 1, name: '篮球', num: 3 },
{ id: 2, name: '玩具', num: 2 },
{ id: 3, name: '铅笔', num: 5 },
]
},
computed: {
totalCount () {
console.log('computed计算属性执行了')
let total = this.list.reduce((sum, item) => sum + item.num, 0)
return total
}
}
})
</script>
</body>
</html>
运行结果:
“computed计算属性执行了”在控制台打印了一次
【分析】
- 依赖追踪:
totalCount
是一个计算属性,Vue 会自动追踪它所依赖的数据。在这个例子中,totalCount
依赖于this.list
。 - 缓存特性: 计算属性会在初次计算后缓存其结果。它会在依赖的数据没有变化的情况下,避免重复计算。由于在页面渲染时
totalCount
常量只会被计算一次,因此 “computed计算属性执行了” 只打印了一次。 - 重新计算: 只有在
list
数据发生变化时,计算属性才会重新计算并更新其缓存值。
②methods计算方法
<body>
<div id="app">
<h3>礼物清单🛒<span>{{ totalCount() }}</span></h3>
<h3>礼物清单🛒<span>{{ totalCount() }}</span></h3>
<h3>礼物清单🛒<span>{{ totalCount() }}</span></h3>
<table>
<tr>
<th>名字</th>
<th>数量</th>
</tr>
<tr v-for="(item, index) in list" :key="item.id">
<td>{{ item.name }}</td>
<td>{{ item.num }}个</td>
</tr>
</table>
<p>礼物总数:{{ totalCount() }} 个</p>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
const app = new Vue({
el: '#app',
data: {
// 现有的数据
list: [
{ id: 1, name: '篮球', num: 3 },
{ id: 2, name: '玩具', num: 2 },
{ id: 3, name: '铅笔', num: 5 },
]
},
methods:{
totalCount () {
console.log('methods方法执行了')
let total = this.list.reduce((sum, item) => sum + item.num, 0)
return total
}
}
})
</script>
</body>
运行结果:
“methods方法执行了”在控制台打印了四次
【分析】
- 直接调用:
totalCount()
是一个方法。每次模板对它进行访问时,都会执行这个方法。 - 调用次数:
totalCount()
被调用了四次。 - 多次打印:每次渲染时,不管
list
是否变化,totalCount()
方法都被重新调用,从而打印 “methods方法执行了” 四次。
三、完整写法
计算属性默认的简写,只能读取访问,不能 “修改”。如果要 “修改” , 需要写计算属性的完整写法。
【示例】
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div id="app">
姓:<input type="text" v-model="firstName"><br>
名:<input type="text" v-model="lastName"><br>
<p>姓名:{{fullName}}</p>
<button @click="changeName">修改姓名</button>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
const app = new Vue({
el: '#app',
data:{
firstName:'吕',
lastName:'布'
},
computed: {
// 默认简写→获取
/* fullName(){
return this.firstName+this.lastName
} */
// 完整写法→获取+设置
fullName:{
/* 当fullName计算属性被获取求值时,执行 get 方法(有缓存会优先获取缓存)
将返回值作为求值的结果 */
get(){
return this.firstName+this.lastName
},
/* 当fullName计算属性被修改赋值时,执行 set 方法
修改的值会被传递给set方法的形参
slice 方法:用来返回一个新的数组或字符串,包含原始数组或字符串的一部分。
slice(start, end);
start:开始索引(可选):表示从哪个位置开始提取子字符串(包含该位置的字符)。
end:结束索引(可选):表示提取到哪个位置(不包含该位置的字符)。
*/
set(value){
this.firstName=value.slice(0,1)
this.lastName=value.slice(1)
}
}
},
methods: {
changeName(){
this.fullName='刘备'
}
}
})
</script>
</body>
</html>
运行结果:
点击“修改姓名”后:
【案例——成绩管理】
根据所学知识实现以下效果:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="stylesheet" href="./styles/index.css" />
<title>成绩管理</title>
</head>
<body>
<div id="app" class="score-case">
<div class="table">
<table>
<thead>
<tr>
<th>编号</th>
<th>科目</th>
<th>成绩</th>
<th>操作</th>
</tr>
</thead>
<tbody v-if="list.length>0">
<tr v-for="(item,index) in list" :key="item.id">
<td>{{index+1}}</td>
<td>{{item.subject}}</td>
<td :class="{red:item.score<60}">{{item.score}}</td>
<td><a href="#" @click="del(item.id)">删除</a></td>
</tr>
</tbody>
<tbody v-else>
<tr>
<td colspan="5">
<span class="none">暂无数据</span>
</td>
</tr>
</tbody>
<tfoot>
<tr>
<td colspan="5">
<span>总分:{{totalScore}}</span>
<span style="margin-left: 50px">平均分:{{averageScore}}</span>
</td>
</tr>
</tfoot>
</table>
</div>
<div class="form">
<div class="form-item">
<div class="label">科目:</div>
<div class="input">
<input
type="text"
placeholder="请输入科目"
v-model.trim="subject"
/>
</div>
</div>
<div class="form-item">
<div class="label">分数:</div>
<div class="input">
<input
type="text"
placeholder="请输入分数"
v-model.number="score"
/>
</div>
</div>
<div class="form-item">
<div class="label"></div>
<div class="input">
<button class="submit" @click="add()">添加</button>
</div>
</div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
const app = new Vue({
el: '#app',
data: {
list: [
{ id: 1, subject: '语文', score: 20 },
{ id: 7, subject: '数学', score: 99 },
{ id: 12, subject: '英语', score: 70 },
],
subject: '',
score: ''
},
methods: {
/* filter方法:用于创建一个新数组,包含所有通过测试的元素
测试条件是每个元素的 id 不等于要删除的 id */
del(id){
this.list=this.list.filter(item=>item.id!==id)
},
/* add方法:负责在列表的开头添加一条新的成绩记录 */
add(){
if(!this.subject){
alert("请输入科目")
return
}
if(typeof this.score!=='number'){
alert("请输入正确的成绩")
return
}
/* unshift方法:用于在数组的开头添加一个或多个元素,并返回新数组的长度
id: 使用当前时间戳作为新成绩的唯一标识
+new Date():将当前日期转换为毫秒数,确保每条记录的 id 是唯一的 */
this.list.unshift({
id:+new Date(),
subject:this.subject,
score:this.score
})
this.subject=''
this.score=''
}
},
computed:{
/* reduce方法:用于将数组中的所有元素汇总为一个单一的值
(sum, item) => sum + item.score: 一个累加函数 */
totalScore(){
return this.list.reduce((sum,item)=>sum+item.score,0)
},
averageScore(){
if(this.list.length===0){
return 0
}
/* .toFixed()方法:用于将一个数字格式化为字符串,保留指定的小数位数 */
return (this.totalScore/this.list.length).toFixed(2)
}
}
})
</script>
</body>
</html>
运行结果:
删除语文成绩后:
添加美术成绩后:
删除全部成绩后: