Vue2 计算属性
Vue2计算属性(Computed Properties)
计算属性的概念与作用
计算属性是 Vue 2 中一种用于声明式地基于响应式依赖进行计算并返回新值的机制。它本质上是一个被缓存的、依赖于其他响应式数据的派生属性。
计算属性解决的核心问题是:
- 当需要根据已有响应式数据(如
data中的属性)动态派生出新值时,避免在模板中编写冗余或复杂的逻辑表达式; - 确保该派生值具备响应式更新能力——即当其依赖的数据发生变化时,计算属性会自动重新求值,并触发视图更新;
- 利用缓存机制提升性能:只要依赖项未改变,多次访问同一计算属性将直接返回缓存结果,而非重复执行求值函数。
关键特征总结:
- 基于现有响应式数据计算得出;
- 具有依赖追踪与自动更新能力;
- 求值结果被缓存,仅在依赖变化时重新计算;
- 语法上表现为属性,而非方法,调用时不加括号。
计算属性的基本语法结构
使用计算属性需严格遵循两个步骤:声明与使用。
声明计算属性
计算属性必须在组件选项的 computed 配置项中声明,该配置项与 data、methods 等同级,属于组件选项对象的顶层属性。
1 | new Vue({ |
注意事项:
computed是一个对象,其每个键名即为计算属性名(如total),对应值必须是一个无参函数;- 函数内部可通过
this访问当前 Vue 实例,从而读取data、其他computed属性或props中的响应式数据; - 必须使用
return语句返回计算结果,否则属性值为undefined; - 不得在函数内执行副作用操作(如修改
data、发起请求等),计算属性应保持纯函数特性。
使用计算属性
在模板中使用计算属性的方式与使用 data 中的普通属性完全一致:
- 通过双花括号插值
{{ total }}渲染; - 在指令中作为表达式使用,如
v-bind:title="total"或v-if="total > 0"; - 不可添加括号(即不可写作
{{ total() }}),因其本质是属性而非方法。
1 | <div> |
错误示例:
{{ total() }}将导致运行时错误,控制台提示类似TypeError: total is not a function,因为 Vue 将computed中定义的函数封装为响应式属性,而非可调用方法。
计算属性的响应式原理与执行流程
依赖收集与缓存机制
Vue 在初始化计算属性时,会执行一次求值函数,并在执行过程中自动追踪所有通过 this 访问的响应式属性(如 this.list、this.list[0].count)。这些被访问的属性即为该计算属性的依赖项。
- 首次访问
total时,Vue 执行total()函数,收集list及其内部count属性为依赖; - 求值结果被缓存,后续对
total的访问直接返回缓存值; - 当任一依赖项(如
list[0].count)发生变更时,Vue 触发依赖通知,标记total为“脏”(dirty),并在下次访问时重新执行求值函数并更新缓存。
执行流程示意图
- 模板渲染阶段,遇到
{{ total }}表达式; - Vue 查找
computed中是否存在total; - 若存在且未被标记为“脏”,则返回缓存值;
- 若为首次访问或已被标记为“脏”,则执行
total()函数; - 函数执行中访问
this.list,触发依赖收集; - 函数执行完毕,
return的值被缓存并返回给模板; - 模板将该值插入 DOM 对应位置。
实战案例:礼物总数累加求和
需求分析
给定一个礼物列表 list,每项包含 name 和 count 字段,要求在页面中动态显示所有礼物数量之和。
完整实现代码
1 | // Vue 组件脚本部分 |
1 | <!-- Vue 组件模板部分 --> |
响应式验证
- 修改
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-model、v-bind 等指令中直接使用 |
仅可在事件处理器(如 @click="handleClick")或显式调用中使用 |
核心差异:缓存机制与执行行为
计算属性的缓存特性(核心优势)
计算属性具备惰性求值与自动缓存能力:
- 首次访问时,Vue 执行其 getter 函数,将返回值缓存;
- 后续访问同一计算属性时,直接返回缓存值,不重新执行 getter;
- 仅当其依赖的响应式数据发生变更时,缓存失效,getter 重新执行并更新缓存。
性能意义:对于涉及复杂运算(如数组
reduce、深度遍历、格式化等)的派生值,缓存可显著避免重复计算,提升渲染性能。
示例说明(基于礼物清单场景)
1 | new Vue({ |
- 若模板中五处使用
{{ totalPrice }},控制台仅输出一次"计算属性执行了"; - 修改
gifts[0].price = 4后,totalPrice重新计算,控制台新增一次输出; - 此后再次读取
totalPrice,不再触发计算,直接返回缓存值。
方法的无缓存特性(执行行为)
方法每次调用均重新执行函数体,无任何缓存机制:
- 模板中每出现一次
{{ methodName() }},即触发一次函数调用; - 即使依赖数据未变,每次渲染均会重复执行;
- 数据变更后,所有调用点均会重新执行。
性能风险:若方法内含高开销计算,在模板中多次调用将导致严重性能损耗。
示例对比(强制改写为方法)
1 | new Vue({ |
- 模板中五处使用
{{ 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.$http、console.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。
为什么需要支持设置的计算属性?
在某些业务场景中,计算属性不仅需反映底层数据的状态,还需支持反向更新底层数据。典型示例如下:
- 基于
firstName和lastName拼接生成fullName; - 用户通过输入框直接修改
fullName(如将“刘备”改为“吕布”); - 系统需自动将新值解析为
firstName = "吕"、lastName = "布",并同步更新原始数据。
此类需求要求计算属性具备双向绑定能力,即同时支持获取(get)与设置(set)逻辑。
计算属性的完整写法
语法结构
计算属性的完整写法必须定义为一个对象,该对象包含两个显式方法:get 和 set。
1 | computed: { |
get 方法的作用与实现
get方法在模板中读取计算属性(如{{ fullName }})或 JavaScript 中访问this.fullName时被调用;- 必须返回一个值,该值作为计算属性的当前值;
- 支持 Vue 的缓存机制:只要其依赖的响应式数据未变化,多次访问将直接返回缓存结果,不重复执行函数。
示例实现:
1 | get() { |
set 方法的作用与实现
set方法在对计算属性执行赋值操作(如this.fullName = '新姓名')时被调用;- 其唯一形参
value接收赋值表达式的右值; - 开发者需在此方法内编写业务逻辑,将
value解析并分发至底层响应式数据; set方法本身无返回值要求,其职责是触发源数据变更。
示例实现(字符串截取解析):
1 | set(value) { |
完整代码示例与验证:改名卡
初始模板结构
1 | <div id="app"> |
Vue 实例配置
1 | new Vue({ |
行为验证要点
- 读取验证:修改
firstName或lastName,fullName实时更新,证明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)是调试解析逻辑的有效手段; - 修改
firstName或lastName不会触发set,仅触发get;仅对计算属性本身赋值才会触发set。
本节总结
- 计算属性的默认简写形式等价于仅定义
get方法,适用于只读场景; - 完整写法必须以对象形式声明,显式提供
get与set两个函数; get函数负责定义如何从源数据派生计算结果,其返回值即为属性值;set函数负责定义如何将新值解析并反向同步至源数据,其形参value即为被赋的值;- 双向绑定能力并非计算属性的默认特性,而是通过完整写法主动赋予的扩展能力;
- 实际开发中,应依据具体业务需求审慎选择写法:简写优先,完整写法按需启用。
Vue2 综合案例教程:学生成绩管理系统
案例概述
本案例实现一个完整的学生成绩管理前端界面,包含数据渲染、动态增删、条件样式控制与统计计算四大核心功能。系统采用 Vue 2 框架开发,严格遵循响应式编程原则,综合运用指令、计算属性、事件处理等关键技术点。
功能需求说明
- 数据渲染:基于数组动态生成表格行,支持空状态提示
- 条件高亮:对低于 60 分的成绩项应用红色样式
- 数据增删:支持通过表单添加新成绩记录;支持按条目删除已有记录
- 统计计算:实时计算总分与平均分,并在无数据时返回合理默认值
页面结构设计
页面采用左右布局:
- 左侧为
<table>元素,包含<thead>(表头)、<tbody>(成绩数据区)与<tfoot>(统计信息区) - 右侧为
<form>表单,含两个输入框(科目、分数)与“添加”按钮 - 数据层定义三个响应式属性:
list: 存储成绩对象的数组,每个对象含id、subject、score字段subject: 绑定科目输入框的字符串型响应式属性score: 绑定分数输入框的数值型响应式属性
1 | <style> |
数据渲染实现
条件渲染:互斥显示数据区与空提示
HTML 结构中存在两个 <tbody> 节点:一个用于展示成绩数据,另一个显示“暂无数据”。二者逻辑互斥,不可同时存在。Vue 2 中应使用 v-if/v-else 实现节点级条件渲染,而非 v-show(后者仅控制 CSS display 属性,不销毁 DOM 节点)。
1 | <table> |
关键说明:
v-if/v-else确保 DOM 树中始终仅存在一个<tbody>节点,符合 HTML 表格语义规范;colspan="3"保证空提示横跨全部三列。
列表渲染:动态生成成绩行
使用 v-for 指令遍历 list 数组,为每条成绩生成 <tr> 行元素。需注意以下实践要点:
- 唯一键值:为每个
<tr>设置:key属性,优先使用数据项的id字段,确保 Vue 的虚拟 DOM Diff 算法高效识别节点变更 - 序号动态生成:行号使用
index + 1计算,避免硬编码,保障连续性与可维护性
1 | <tbody v-if="list.length > 0"> |
动态样式控制:不及格成绩高亮
通过 :class 绑定对象语法实现条件类名切换。当 item.score < 60 为真时,red 类被添加至 <td> 元素;否则移除。该语法适用于单一类名的开关控制场景。
技术对比:若需同时控制多个类名(如
red与bold),应使用数组语法: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 | methods: { |
原理说明:
filter()返回新数组,直接赋值给this.list触发 Vue 响应式更新,视图自动重绘。
添加功能:表单绑定与数据校验
添加功能需完成表单数据绑定、输入验证、数据追加与状态重置四步操作。
步骤一:双向数据绑定
使用 v-model 指令绑定输入框与响应式属性,并结合修饰符增强健壮性:
v-model.trim:自动去除科目输入内容首尾空白字符v-model.number:强制将分数输入转换为数值类型(非数字输入返回NaN)
1 | <input v-model.trim="subject" placeholder="请输入科目" /> |
步骤二:输入验证逻辑
在 handleAdd() 方法中执行前置校验:
- 检查
subject是否为空(空字符串取反为true) - 检查
score是否为有效数字(typeof score === 'number' && !isNaN(score))
1 | handleAdd() { |
步骤三:数据追加与状态重置
验证通过后,使用 Array.prototype.unshift() 将新成绩对象插入数组头部,并清空表单字段:
1 | handleAdd() { |
设计说明:
unshift()实现头部插入,push()可实现尾部插入;清空操作确保用户下一次输入前表单处于初始状态。
统计计算功能实现
计算属性原理
统计功能依赖于现有数据的动态派生,必须使用 computed 属性而非 data 或 methods:
data属性为静态初始值,无法响应数据变化methods需手动调用且无缓存机制,频繁调用影响性能computed属性具有响应式依赖追踪与缓存特性,仅在依赖数据变更时重新求值
总分计算:reduce() 方法应用
定义 totalScore 计算属性,对 list 数组所有 score 值求和:
1 | computed: { |
方法解析:
reduce()接收累加器sum与当前项item,初始值设为0;每次迭代返回sum + item.score,最终得到总分。
平均分计算:依赖其他计算属性
定义 averageScore 计算属性,复用 totalScore 并除以 list.length。需增加边界判断,避免除零错误:
1 | computed: { |
关键处理:
toFixed(2)保留两位小数;if (this.list.length === 0)防止0 / 0导致NaN,确保无数据时返回0。
模板中渲染统计结果
在 <tfoot> 区域使用双大括号语法渲染计算属性:
1 | <tfoot> |
完整代码
1 |
|
知识点总结与最佳实践
| 功能模块 | 核心语法 | 关键要点 | 应用场景 |
|---|---|---|---|
| 条件渲染 | 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 实现相同效果。
