Vue2计算属性(Computed Properties)

计算属性的概念与作用

计算属性是 Vue 2 中一种用于声明式地基于响应式依赖进行计算并返回新值的机制。它本质上是一个被缓存的、依赖于其他响应式数据的派生属性

计算属性解决的核心问题是:

  • 当需要根据已有响应式数据(如 data 中的属性)动态派生出新值时,避免在模板中编写冗余或复杂的逻辑表达式;
  • 确保该派生值具备响应式更新能力——即当其依赖的数据发生变化时,计算属性会自动重新求值,并触发视图更新;
  • 利用缓存机制提升性能:只要依赖项未改变,多次访问同一计算属性将直接返回缓存结果,而非重复执行求值函数。

关键特征总结

  • 基于现有响应式数据计算得出;
  • 具有依赖追踪与自动更新能力;
  • 求值结果被缓存,仅在依赖变化时重新计算;
  • 语法上表现为属性,而非方法,调用时不加括号。

计算属性的基本语法结构

使用计算属性需严格遵循两个步骤:声明使用

声明计算属性

计算属性必须在组件选项的 computed 配置项中声明,该配置项与 datamethods 等同级,属于组件选项对象的顶层属性。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
new Vue({
data() {
return {
list: [
{ name: '篮球', count: 1 },
{ name: '玩具', count: 2 },
{ name: '铅笔', count: 5 }
]
}
},
computed: {
// 声明一个名为 total 的计算属性
total() {
// 函数体内编写求值逻辑
// 必须通过 return 显式返回最终结果
return this.list.reduce((sum, item) => sum + item.count, 0)
}
}
});

注意事项

  • computed 是一个对象,其每个键名即为计算属性名(如 total),对应值必须是一个无参函数
  • 函数内部可通过 this 访问当前 Vue 实例,从而读取 data、其他 computed 属性或 props 中的响应式数据;
  • 必须使用 return 语句返回计算结果,否则属性值为 undefined
  • 不得在函数内执行副作用操作(如修改 data、发起请求等),计算属性应保持纯函数特性。

使用计算属性

在模板中使用计算属性的方式与使用 data 中的普通属性完全一致:

  • 通过双花括号插值 {{ total }} 渲染;
  • 在指令中作为表达式使用,如 v-bind:title="total"v-if="total > 0"
  • 不可添加括号(即不可写作 {{ total() }}),因其本质是属性而非方法。
1
2
3
4
5
6
7
8
<div>
<ul>
<li v-for="item in list" :key="item.name">
{{ item.name }} × {{ item.count }}
</li>
</ul>
<p>礼物总数:{{ total }}</p> <!-- 正确:直接使用属性名 -->
</div>

错误示例{{ total() }} 将导致运行时错误,控制台提示类似 TypeError: total is not a function,因为 Vue 将 computed 中定义的函数封装为响应式属性,而非可调用方法。

计算属性的响应式原理与执行流程

依赖收集与缓存机制

Vue 在初始化计算属性时,会执行一次求值函数,并在执行过程中自动追踪所有通过 this 访问的响应式属性(如 this.listthis.list[0].count)。这些被访问的属性即为该计算属性的依赖项

  • 首次访问 total 时,Vue 执行 total() 函数,收集 list 及其内部 count 属性为依赖;
  • 求值结果被缓存,后续对 total 的访问直接返回缓存值;
  • 当任一依赖项(如 list[0].count)发生变更时,Vue 触发依赖通知,标记 total 为“脏”(dirty),并在下次访问时重新执行求值函数并更新缓存。

执行流程示意图

  1. 模板渲染阶段,遇到 {{ total }} 表达式;
  2. Vue 查找 computed 中是否存在 total
  3. 若存在且未被标记为“脏”,则返回缓存值;
  4. 若为首次访问或已被标记为“脏”,则执行 total() 函数;
  5. 函数执行中访问 this.list,触发依赖收集;
  6. 函数执行完毕,return 的值被缓存并返回给模板;
  7. 模板将该值插入 DOM 对应位置。

实战案例:礼物总数累加求和

需求分析

给定一个礼物列表 list,每项包含 namecount 字段,要求在页面中动态显示所有礼物数量之和。

完整实现代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// Vue 组件脚本部分
new Vue({
el: '.gift-container',
data() {
return {
list: [
{ name: '篮球', count: 1 },
{ name: '玩具', count: 2 },
{ name: '铅笔', count: 5 }
]
}
},
computed: {
total() {
// 使用 Array.prototype.reduce 方法对 list 中每个 item.count 求和
// 初始值设为 0,确保空数组时返回 0
return this.list.reduce((sum, item) => sum + item.count, 0)
}
}
})
1
2
3
4
5
6
7
8
9
10
11
12
<!-- Vue 组件模板部分 -->
<div class="gift-container">
<h2>我的礼物清单</h2>
<ul class="gift-list">
<li v-for="(item, index) in list" :key="index">
{{ item.name }} × {{ item.count }}
</li>
</ul>
<div class="summary">
<strong>礼物总数:</strong>{{ total }}
</div>
</div>

响应式验证

  • 修改 data.list 中任意 count 值(如将篮球数量由 1 改为 3),total 将自动更新为 10
  • 通过 Vue Devtools 修改数据,或在控制台执行 this.list[0].count = 4,视图立即同步刷新;
  • 此过程无需手动调用方法或触发事件,完全由 Vue 的响应式系统保障。

常见误区与注意事项

与 methods 的本质区别

虽然计算属性以函数形式声明,但其设计目的、执行时机与缓存行为均不同于 methods

特性 计算属性(computed) 方法(methods)
缓存 有:依赖不变时复用缓存值 无:每次渲染都重新执行
触发时机 仅在依赖变化或首次访问时求值 每次调用(包括模板中每次出现)均执行
模板调用语法 {{ total }}(不加括号) {{ getTotal() }}(必须加括号)
适用场景 依赖响应式数据的派生值,强调声明式与性能 需要显式调用的业务逻辑、事件处理器

重要提醒:若将相同逻辑写入 methods,虽功能可达,但会导致不必要的重复计算,降低性能,违背 Vue 的响应式设计哲学。

其他注意事项

  • 计算属性函数内不得使用箭头函数定义,因其会绑定错误的 this 上下文(应使用普通函数);
  • 避免在计算属性中执行异步操作或修改响应式数据,否则破坏响应式链路与可预测性;
  • 复杂逻辑建议拆分为多个细粒度计算属性,提升可读性与可维护性;
  • 若需监听计算属性变化,应使用 watch 监听该计算属性名,而非其依赖项。

小结

本节系统讲解了 Vue 2 中计算属性的核心概念、语法规范、执行机制及典型应用:

  • 定义:计算属性是基于响应式依赖声明式计算得出的新属性,具备自动更新与缓存能力;
  • 声明:在 computed 选项中以函数形式定义,函数必须 return 结果;
  • 使用:在模板中以普通属性方式访问({{ propertyName }}),严禁加括号
  • 优势:相比 methods,提供更优的性能表现与更清晰的数据流表达;
  • 原则:保持纯函数特性,专注数据派生,避免副作用。

掌握计算属性是构建高效、可维护 Vue 应用的基础能力。


计算属性(computed)与方法(methods)的区别

核心概念辨析

在 Vue 2 中,computed(计算属性)与 methods(方法)均用于封装逻辑,但二者在设计目的、执行机制、使用场景及性能表现上存在本质区别。初学者需明确其边界,避免误用。

设计定位与职责划分

  • 计算属性(computed)专用于响应式地处理数据并派生新值。其核心职责是:基于响应式依赖(如 data 中的属性)进行同步计算,返回一个确定的、可缓存的结果值。该结果可被多次读取,且仅在依赖项变更时重新求值。
  • 方法(methods)专用于为组件实例提供可调用的行为接口,以执行副作用操作或业务逻辑。典型用途包括:

    • 触发事件处理(如按钮点击)
    • 修改响应式数据(如 this.count++
    • 发起网络请求(如 axios.get()
    • 执行非响应式、非派生性的程序逻辑

关键结论计算属性用于“求值”,方法用于“做事”。二者不可混用其核心职责。

语法定义与调用方式

维度 计算属性(computed) 方法(methods)
定义位置 computed: { ... } 配置项中 methods: { ... } 配置项中
定义形式 函数(返回值即属性值),支持 getter/setter 普通函数(无隐式返回要求)
模板中使用 直接作为属性访问不加括号(如 {{ totalPrice }} 必须显式调用需加括号(如 {{ getTotalPrice() }}
响应式绑定 支持在 v-modelv-bind 等指令中直接使用 仅可在事件处理器(如 @click="handleClick")或显式调用中使用

核心差异:缓存机制与执行行为

计算属性的缓存特性(核心优势)

计算属性具备惰性求值与自动缓存能力:

  • 首次访问时,Vue 执行其 getter 函数,将返回值缓存
  • 后续访问同一计算属性时,直接返回缓存值不重新执行 getter
  • 仅当其依赖的响应式数据发生变更时,缓存失效,getter 重新执行并更新缓存。

性能意义:对于涉及复杂运算(如数组 reduce、深度遍历、格式化等)的派生值,缓存可显著避免重复计算,提升渲染性能。

示例说明(基于礼物清单场景)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
new Vue({
data() {
return {
gifts: [
{ name: '书', price: 33 },
{ name: '笔', price: 1 }
]
}
},
computed: {
// 计算属性:总价格
totalPrice() {
console.log('计算属性执行了') // 仅在依赖变更时打印
return this.gifts.reduce((sum, item) => sum + item.price, 0)
}
}
})
  • 若模板中五处使用 {{ totalPrice }}控制台仅输出一次 "计算属性执行了"
  • 修改 gifts[0].price = 4 后,totalPrice 重新计算,控制台新增一次输出
  • 此后再次读取 totalPrice不再触发计算,直接返回缓存值。

方法的无缓存特性(执行行为)

方法每次调用均重新执行函数体,无任何缓存机制:

  • 模板中每出现一次 {{ methodName() }},即触发一次函数调用;
  • 即使依赖数据未变,每次渲染均会重复执行;
  • 数据变更后,所有调用点均会重新执行。

性能风险:若方法内含高开销计算,在模板中多次调用将导致严重性能损耗。

示例对比(强制改写为方法)

1
2
3
4
5
6
7
8
9
10
new Vue({
// ... data 同上
methods: {
// 方法:模拟相同逻辑
getTotalPrice() {
console.log('方法执行了') // 每次调用均打印
return this.gifts.reduce((sum, item) => sum + item.price, 0)
}
}
})
  • 模板中五处使用 {{ getTotalPrice() }}控制台输出五次 "方法执行了"
  • 修改 gifts[0].price = 4 后,再次触发五次调用与五次输出
  • 无缓存保护,计算成本随调用次数线性增长。

二者使用原则与最佳实践

选择准则

场景描述 推荐方案 原因说明
基于响应式数据派生新值(如过滤、求和、格式化) computed 利用缓存避免重复计算,保证响应式更新一致性
处理用户交互事件(如点击、输入) methods 需主动触发,且常伴随状态修改、异步请求等副作用操作
需要参数的动态计算(如 filterBy(type) methods 计算属性不支持传参;若需参数化,应使用方法或结合 computed + props
依赖非响应式数据(如 Date.now() methods 计算属性仅响应 data/props 中的响应式依赖,无法监听时间等外部变化

注意事项

  • 禁止直接修改计算属性:计算属性默认为只读(getter-only)。若尝试赋值(如 this.totalPrice = 100),Vue 将抛出警告,且不会生效。如需双向绑定,须显式定义 set(见进阶章节)。
  • 避免在计算属性中执行副作用:计算属性应为纯函数(无 this.$httpconsole.log 等副作用)。副作用应移至 methods 或生命周期钩子中。
  • 方法调用需严格加括号{{ handler }}(错误,仅传递函数引用) vs {{ handler() }}(正确,执行函数)。

总结:关键对比表

特性 计算属性(computed) 方法(methods)
主要用途 派生响应式数据,获取计算结果 封装可复用的业务逻辑与副作用操作
执行时机 依赖变更时惰性求值,自动缓存结果 每次调用均重新执行,无缓存
模板调用语法 {{ propertyName }}(无括号) {{ methodName() }}(必须加括号)
定义位置 computed: { ... } methods: { ... }
性能特征 高效率(复杂计算仅执行一次) 低效率(重复调用导致重复计算)
适用场景 过滤列表、计算总数、格式化日期等派生值 事件处理、数据修改、API 请求等操作

核心口诀求值用 computed,做事用 methods;模板读值不加括号,调用方法务必加括号。


Vue2 计算属性的完整写法:支持读取与设置

计算属性(Computed Property)是 Vue 实例中用于声明式地描述派生状态的核心机制。它基于响应式依赖进行缓存,仅在相关响应式数据发生变化时重新求值。

计算属性默认仅支持读取(get)操作,其简写形式本质上是 get 函数的语法糖。当尝试对仅定义了 get 的计算属性进行赋值时,Vue 将抛出运行时错误:Computed property "xxx" was assigned to but it has no setter

为什么需要支持设置的计算属性?

在某些业务场景中,计算属性不仅需反映底层数据的状态,还需支持反向更新底层数据。典型示例如下:

  • 基于 firstNamelastName 拼接生成 fullName
  • 用户通过输入框直接修改 fullName(如将“刘备”改为“吕布”);
  • 系统需自动将新值解析为 firstName = "吕"lastName = "布",并同步更新原始数据。

此类需求要求计算属性具备双向绑定能力,即同时支持获取(get)与设置(set)逻辑。

计算属性的完整写法

语法结构

计算属性的完整写法必须定义为一个对象,该对象包含两个显式方法:getset

1
2
3
4
5
6
7
8
9
10
11
12
13
computed: {
fullName: {
get() {
// 返回计算结果,用于读取
return this.firstName + this.lastName;
},
set(value) {
// 接收被赋的新值,用于更新源数据
// value 为赋值表达式右侧的值(如:this.fullName = '吕布' 中的 '吕布')
// 在此编写解析与赋值逻辑
}
}
}

get 方法的作用与实现

  • get 方法在模板中读取计算属性(如 {{ fullName }})或 JavaScript 中访问 this.fullName 时被调用;
  • 必须返回一个值,该值作为计算属性的当前值;
  • 支持 Vue 的缓存机制:只要其依赖的响应式数据未变化,多次访问将直接返回缓存结果,不重复执行函数。

示例实现:

1
2
3
get() {
return this.firstName + this.lastName;
}

set 方法的作用与实现

  • set 方法在对计算属性执行赋值操作(如 this.fullName = '新姓名')时被调用;
  • 其唯一形参 value 接收赋值表达式的右值;
  • 开发者需在此方法内编写业务逻辑,将 value 解析并分发至底层响应式数据;
  • set 方法本身无返回值要求,其职责是触发源数据变更。

示例实现(字符串截取解析):

1
2
3
4
5
set(value) {
// 假设规则:首字符为姓,其余字符为名
this.firstName = value.slice(0, 1); // 取第0位(含)至第1位(不含)
this.lastName = value.slice(1); // 取第1位(含)至末尾
}

完整代码示例与验证:改名卡

初始模板结构

1
2
3
4
5
6
<div id="app">
<p>姓:<input v-model="firstName"></p>
<p>名:<input v-model="lastName"></p>
<p>全名:{{ fullName }}</p>
<button @click="changeName">改名卡</button>
</div>

Vue 实例配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
new Vue({
el: '#app',
data: {
firstName: '刘',
lastName: '备'
},
computed: {
fullName: {
get() {
return this.firstName + this.lastName;
},
set(value) {
// 解析新全名:首字符为姓,后续字符为名
this.firstName = value.slice(0, 1);
this.lastName = value.slice(1);
}
}
},
methods: {
changeName() {
this.fullName = '吕布'; // 触发 set 方法
}
}
});

行为验证要点

  • 读取验证:修改 firstNamelastNamefullName 实时更新,证明 get 正常工作;
  • 设置验证
    • 执行 this.fullName = '貂蝉' 后,firstName 应变为 '貂'lastName 应变为 '蝉'
    • 执行 this.fullName = '黄忠' 后,firstName 应变为 '黄'lastName 应变为 '忠'
  • 错误防护验证:若仅定义 get 而未定义 set,执行赋值将抛出 Error: Computed property "fullName" was assigned to but it has no setter

完整写法的使用原则与最佳实践

选择简写还是完整写法?

  • 优先使用简写形式:当计算属性仅用于派生展示、无需反向更新时,采用 computed: { fullName() { return ...; } } 更简洁、可读性更高;
  • 仅在必要时使用完整写法:当明确存在双向数据流需求(如表单联动、格式化输入等),才引入 get/set 对象结构。

set 方法设计注意事项

  • 解析逻辑需健壮value 可能为任意字符串,应考虑空值、超长、特殊字符等边界情况;
  • 避免副作用set 内不应执行异步操作、DOM 操作或发起网络请求;所有状态更新应严格限定于响应式数据赋值;
  • 保持单向数据流清晰性set 的目标是还原源数据,而非创建新状态;确保 get 的输出始终与 set 更新后的源数据一致。

性能与调试提示

  • get 函数的缓存特性可显著提升性能,但 set 函数无缓存,每次赋值均会执行;
  • set 中添加 console.log(value) 是调试解析逻辑的有效手段;
  • 修改 firstNamelastName 不会触发 set,仅触发 get;仅对计算属性本身赋值才会触发 set

本节总结

  • 计算属性的默认简写形式等价于仅定义 get 方法,适用于只读场景;
  • 完整写法必须以对象形式声明,显式提供 getset 两个函数;
  • get 函数负责定义如何从源数据派生计算结果,其返回值即为属性值;
  • set 函数负责定义如何将新值解析并反向同步至源数据,其形参 value 即为被赋的值;
  • 双向绑定能力并非计算属性的默认特性,而是通过完整写法主动赋予的扩展能力;
  • 实际开发中,应依据具体业务需求审慎选择写法:简写优先,完整写法按需启用

Vue2 综合案例教程:学生成绩管理系统

案例概述

本案例实现一个完整的学生成绩管理前端界面,包含数据渲染、动态增删、条件样式控制与统计计算四大核心功能。系统采用 Vue 2 框架开发,严格遵循响应式编程原则,综合运用指令、计算属性、事件处理等关键技术点。

功能需求说明

  • 数据渲染:基于数组动态生成表格行,支持空状态提示
  • 条件高亮:对低于 60 分的成绩项应用红色样式
  • 数据增删:支持通过表单添加新成绩记录;支持按条目删除已有记录
  • 统计计算:实时计算总分与平均分,并在无数据时返回合理默认值

页面结构设计

页面采用左右布局:

  • 左侧为 <table> 元素,包含 <thead>(表头)、<tbody>(成绩数据区)与 <tfoot>(统计信息区)
  • 右侧为 <form> 表单,含两个输入框(科目、分数)与“添加”按钮
  • 数据层定义三个响应式属性:
    • list: 存储成绩对象的数组,每个对象含 idsubjectscore 字段
    • subject: 绑定科目输入框的字符串型响应式属性
    • score: 绑定分数输入框的数值型响应式属性
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<style>
/* 全局重置 */
#app {
width: 1000px;
margin: 30px auto;
/* 左右布局:表格、表单放一行 */
display: flex;
gap: 40px; /* 两者中间间距 */
align-items: flex-start;
}
/* 表格样式 */
table th, table td {
border: 1px solid #999;
padding: 10px 26px;
text-align: center;
}
/* 不及格红色文字 */
.red {
color: red;
font-weight: bold;
}
/* 表单区域样式 */
.form-box {
width: 300px;
padding: 20px;
border-radius: 6px;
}
</style>

数据渲染实现

条件渲染:互斥显示数据区与空提示

HTML 结构中存在两个 <tbody> 节点:一个用于展示成绩数据,另一个显示“暂无数据”。二者逻辑互斥,不可同时存在。Vue 2 中应使用 v-if/v-else 实现节点级条件渲染,而非 v-show(后者仅控制 CSS display 属性,不销毁 DOM 节点)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<table>
<thead>
<tr>
<th>序号</th>
<th>科目</th>
<th>分数</th>
<th>操作</th>
</tr>
</thead>
<tbody v-if="list.length > 0">
<!-- 成绩数据渲染区 -->
</tbody>
<tbody v-else>
<tr>
<td colspan="4">暂无数据</td>
</tr>
</tbody>
<tfoot>...</tfoot>
</table>

关键说明v-if/v-else 确保 DOM 树中始终仅存在一个 <tbody> 节点,符合 HTML 表格语义规范;colspan="3" 保证空提示横跨全部三列。

列表渲染:动态生成成绩行

使用 v-for 指令遍历 list 数组,为每条成绩生成 <tr> 行元素。需注意以下实践要点:

  • 唯一键值:为每个 <tr> 设置 :key 属性,优先使用数据项的 id 字段,确保 Vue 的虚拟 DOM Diff 算法高效识别节点变更
  • 序号动态生成:行号使用 index + 1 计算,避免硬编码,保障连续性与可维护性
1
2
3
4
5
6
7
8
<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="#">删除</a></td>
</tr>
</tbody>

动态样式控制:不及格成绩高亮

通过 :class 绑定对象语法实现条件类名切换。当 item.score < 60 为真时,red 类被添加至 <td> 元素;否则移除。该语法适用于单一类名的开关控制场景。

技术对比:若需同时控制多个类名(如 redbold),应使用数组语法 :class="[condition1 ? 'red' : '', condition2 ? 'bold' : '']";但本例仅涉及单类切换,对象语法更简洁高效。

数据增删功能实现

删除功能:事件绑定与数组过滤

删除操作通过 <a> 标签触发,需阻止其默认跳转行为,并传递目标项 ID 进行筛选。

步骤一:阻止默认行为

<a> 标签添加 @click.prevent 事件修饰符,消除点击后页面跳转副作用:

1
<a href="#" @click.prevent="handleDelete(item.id)">删除</a>
步骤二:实现删除逻辑

methods 中定义 handleDelete(id) 方法,利用 Array.prototype.filter() 创建新数组,排除 id 匹配的项,并将结果赋值回 this.list

1
2
3
4
5
methods: {
handleDelete(id) {
this.list = this.list.filter(item => item.id !== id);
}
}

原理说明filter() 返回新数组,直接赋值给 this.list 触发 Vue 响应式更新,视图自动重绘。

添加功能:表单绑定与数据校验

添加功能需完成表单数据绑定、输入验证、数据追加与状态重置四步操作。

步骤一:双向数据绑定

使用 v-model 指令绑定输入框与响应式属性,并结合修饰符增强健壮性:

  • v-model.trim:自动去除科目输入内容首尾空白字符
  • v-model.number:强制将分数输入转换为数值类型(非数字输入返回 NaN
1
2
3
<input v-model.trim="subject" placeholder="请输入科目" />
<input v-model.number="score" placeholder="请输入分数" />
<button @click="handleAdd">添加</button>
步骤二:输入验证逻辑

handleAdd() 方法中执行前置校验:

  • 检查 subject 是否为空(空字符串取反为 true
  • 检查 score 是否为有效数字(typeof score === 'number' && !isNaN(score)
1
2
3
4
5
6
7
8
9
10
11
handleAdd() {
if (!this.subject) {
alert('请输入科目');
return;
}
if (typeof this.score !== 'number' || isNaN(this.score)) {
alert('请输入正确的成绩');
return;
}
// 后续添加逻辑...
}
步骤三:数据追加与状态重置

验证通过后,使用 Array.prototype.unshift() 将新成绩对象插入数组头部,并清空表单字段:

1
2
3
4
5
6
7
8
9
10
handleAdd() {
// ...校验逻辑
this.list.unshift({
id: new Date().getTime(), // 时间戳保证唯一性
subject: this.subject,
score: this.score
});
this.subject = ''; // 清空科目
this.score = ''; // 清空分数
}

设计说明unshift() 实现头部插入,push() 可实现尾部插入;清空操作确保用户下一次输入前表单处于初始状态。


统计计算功能实现

计算属性原理

统计功能依赖于现有数据的动态派生,必须使用 computed 属性而非 datamethods

  • data 属性为静态初始值,无法响应数据变化
  • methods 需手动调用且无缓存机制,频繁调用影响性能
  • computed 属性具有响应式依赖追踪与缓存特性,仅在依赖数据变更时重新求值

总分计算:reduce() 方法应用

定义 totalScore 计算属性,对 list 数组所有 score 值求和:

1
2
3
4
5
computed: {
totalScore() {
return this.list.reduce((sum, item) => sum + item.score, 0);
}
}

方法解析reduce() 接收累加器 sum 与当前项 item,初始值设为 0;每次迭代返回 sum + item.score,最终得到总分。

平均分计算:依赖其他计算属性

定义 averageScore 计算属性,复用 totalScore 并除以 list.length。需增加边界判断,避免除零错误:

1
2
3
4
5
6
7
8
9
computed: {
totalScore() {
return this.list.reduce((sum, item) => sum + item.score, 0);
},
averageScore() {
if (this.list.length === 0) return 0;
return (this.totalScore / this.list.length).toFixed(2);
}
}

关键处理toFixed(2) 保留两位小数;if (this.list.length === 0) 防止 0 / 0 导致 NaN,确保无数据时返回 0

模板中渲染统计结果

<tfoot> 区域使用双大括号语法渲染计算属性:

1
2
3
4
5
6
7
8
9
10
<tfoot>
<tr>
<td colspan="3">总分:</td>
<td>{{ totalScore }}</td>
</tr>
<tr>
<td colspan="3">平均分:</td>
<td>{{ averageScore }}</td>
</tr>
</tfoot>

完整代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>学生成绩管理系统</title>
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<style>
/* 全局重置 */
#app {
width: 1000px;
margin: 30px auto;
/* 左右布局:表格、表单放一行 */
display: flex;
gap: 40px;
/* 两者中间间距 */
align-items: flex-start;
}

/* 表格样式 */
table th,
table td {
border: 1px solid #999;
padding: 10px 26px;
text-align: center;
}

/* 不及格红色文字 */
.red {
color: red;
font-weight: bold;
}

/* 表单区域样式 */
.form-box {
width: 300px;
padding: 20px;
border-radius: 6px;
}
</style>
</head>

<body>
<div id="app">
<!-- 左侧表格 -->
<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.prevent="handleDelete(item.id)">删除</a></td>
</tr>
</tbody>
<tbody v-else>
<tr>
<td colspan="4">暂无数据</td>
</tr>
</tbody>
<tfoot>
<tr>
<td colspan="3">总分:</td>
<td>{{ totalScore }}</td>
</tr>
<tr>
<td colspan="3">平均分:</td>
<td>{{ averageScore }}</td>
</tr>
</tfoot>
</table>

<!-- 右侧表单,套一层div方便单独美化 -->
<div class="form-box">
<input v-model.trim="subject" placeholder="请输入科目" />
<input v-model.number="score" placeholder="请输入分数" />
<button @click="handleAdd">添加</button>
</div>
</div>

<script>
new Vue({
el: '#app',
data() {
return {
subject: '',
score: '',
list: [
{ id: 1, subject: '语文', score: 59 },
{ id: 2, subject: '数学', score: 100 },
{ id: 3, subject: '英语', score: 66 }
],
}
},
methods: {
handleDelete(id) {
this.list = this.list.filter(item => item.id !== id);
},
handleAdd() {
if (!this.subject) {
alert('请输入科目');
return;
}
if (typeof this.score !== 'number' || isNaN(this.score)) {
alert('请输入正确的成绩');
return;
}
// 简单新增逻辑
this.list.unshift({
id: new Date().getTime(), // 时间戳保证唯一性
subject: this.subject,
score: this.score
});
this.subject = ''; // 清空科目
this.score = ''; // 清空分数
}
},
computed: {
totalScore() {
return this.list.reduce((sum, item) => sum + item.score, 0);
},
averageScore() {
if (this.list.length === 0) return 0;
return (this.totalScore / this.list.length).toFixed(2);
}
}

})
</script>
</body>
</html>

知识点总结与最佳实践

功能模块 核心语法 关键要点 应用场景
条件渲染 v-if / v-else 节点级条件控制,满足条件时创建 DOM,否则销毁;不可与 v-show 混用 互斥 UI 区域(如数据区/空提示)
列表渲染 v-for + :key :key 必须唯一且稳定,推荐使用数据 ID;避免使用 index 作为 key 动态生成重复结构(如表格行、列表项)
动态样式 :class="{ className: condition }" 对象语法适用于单类开关;数组语法适用于多类组合 条件化视觉反馈(如状态高亮、错误标记)
表单绑定 v-model + 修饰符 .trim 去除首尾空格;.number 强制转数值;修饰符可链式使用 提升表单数据质量与用户体验
事件处理 @click.prevent .prevent 阻止默认行为;.stop 阻止事件冒泡;修饰符提升代码可读性 处理 <a><form> 等原生元素默认行为
数据操作 filter() / unshift() filter() 创建新数组;unshift()/push() 修改原数组并触发响应式更新 安全高效的数组增删操作
统计计算 computed + reduce() 计算属性自动缓存结果;reduce() 是函数式求和标准方案 基于响应式数据的派生状态计算

重要提醒:所有 v-model 修饰符(.trim.number)均为语法糖,等价于在 input 事件中手动调用 String.prototype.trim()Number()。其本质是简化常见数据预处理逻辑,开发者仍可选择手写 JavaScript 实现相同效果。