主题
指令(下)、计算属性与侦听器(二)
本页对应 指令(下)、计算属性与侦听器,共 7 段视频,约 1 小时 4 分。
学习目标
- 能够使用
v-bind:style对象语法为元素设置动态样式,并正确处理含连字符的 CSS 属性名。 - 能够通过变量控制进度条内层宽度,并结合按钮事件完成不同百分比进度的切换。
- 能够将
v-model应用于文本框、单选按钮组、下拉框、单个复选框和复选框组,并正确绑定value、checked及数组数据。 - 能够声明并使用计算属性,在模板中不加括号调用,并说明其缓存特性及与
methods调用执行次数的区别。 - 能够使用计算属性的
get/set完整写法修改计算属性,并在set中根据修改结果更新依赖数据。 - 能够完成成绩列表渲染,使用
v-for展示数据、v-if控制空状态,并通过动态class对低于 60 分的成绩设置样式。
知识点
先看本节原始结构:代码准备
html
<style>
textarea {
display: block;
width: 240px;
height: 100px;
margin: 10px 0;
}
</style>
<div id="app">
<h3>小黑学习网</h3>
姓名:
<input type="text">
<br><br>
是否单身:
<input type="checkbox">
<br><br>
<!--
前置理解:
1. name: 给单选框加上 name 属性可以分组 → 同一组互相会互斥
2. value: 给单选框加上 value 属性,用于提交给后台的数据
结合 Vue 使用 → v-model
-->
性别:
<input type="radio">男
<input type="radio">女
<br><br>
<!--
前置理解:
1. option 需要设置 value 值,提交给后台
2. select 的 value 值,关联了选中的 option 的 value 值
结合 Vue 使用 → v-model
-->
所在城市:
<select>
<option>北京</option>
<option>上海</option>
<option>成都</option>
<option>南京</option>
</select>
<br><br>
自我描述:
<textarea></textarea>
<button>立即注册</button>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<script>
const app = new Vue({
el: '#app',
data: {
}
})
</script>v-model 的总思路:按控件类型自动选 value 或 checked
为什么需要:常见表单元素都要“取值”和“设值”。如果每个控件都手写事件、读 DOM,代码会散。怎么想:v-model 会根据控件类型自动选取正确方法:输入框、文本域、下拉菜单关联 value;复选框、单选框关联 checked。怎么做:给控件加 v-model="数据名",再在 data 中准备对应类型的数据。易错点:不要把所有控件都当成字符串;单选框、复选框要看选中状态,不能只看显示文字。
输入框与文本域:绑定 value
为什么需要:姓名、自我描述要能随输入变化,也要能由数据反填。怎么想:input:text 和 textarea 都用 value,所以直接 v-model。怎么做:姓名写 v-model="name",文本域写 v-model="desc",data 中给 name: ''、desc: ''。调试时在页面输入“123”,数据会变;在调试工具里改 name,页面也会变,这就是双向。易错点:文本域不要写成 <textarea>{{ desc }}</textarea> 再混用 v-model,直接用 v-model 即可。
单选按钮:name 分组,value 定值,两个 radio 绑同一个数据
为什么需要:性别只能选一个,且提交给后台的是“男/女”这样的值。怎么想:同一组 radio 的 name 要相同,才能互斥;每个 radio 要有 value;两个 radio 都写同一个 v-model。怎么做:<input type="radio" name="gender" value="男" v-model="gender">男,另一个 value="女",data 中 gender: ''。点击女生,gender 变“女”;把数据改成“男”,页面选中男。易错点:value 要和数据候选值一致;页面上的“男/女”是给用户看的,真正收集的是 value,不要以为拿到的是标签文字。
下拉菜单:select 绑 value,option 给 value
为什么需要:城市下拉要能默认选中,也要能改数据。怎么想:select 的 value 关联选中 option 的 value。怎么做:<select v-model="city">,每个 option 写 value="北京" 等,data 中 city: '上海',页面默认选中上海。页面改选北京,数据变北京;数据改成天津,页面也跟着变。易错点:按本节要求给 option 设置 value,避免把显示文字和提交值混在一起。
复选框:单个用布尔,一组用数组
为什么需要:单个“是否单身”只有选中/不选中;一组爱好可以多选。怎么想:单个复选框用布尔值控制 checked;一组复选框要用数组收集多个 value。怎么做:单个写 v-model="single",data 中 single: false;勾选变 true,取消变 false。一组写 v-model="hobbies",每个复选框给不同 value,data 中 hobbies: ['吃饭', '睡觉'],数组里包含哪个 value,哪个就选中。易错点:一组复选框不能只用一个布尔值;取消“睡觉”后,数组里应只剩“吃饭”。
关键改造片段
html
姓名:<input type="text" v-model="name">
是否单身:<input type="checkbox" v-model="single">
性别:
<input type="radio" name="gender" value="男" v-model="gender">男
<input type="radio" name="gender" value="女" v-model="gender">女
所在城市:
<select v-model="city">
<option value="北京">北京</option>
<option value="上海">上海</option>
<option value="成都">成都</option>
<option value="南京">南京</option>
</select>
自我描述:<textarea v-model="desc"></textarea>js
data: {
name: '',
single: false,
gender: '',
city: '上海',
desc: ''
}易错点小结
单选框忘记 name 或 value;两个 radio 没有绑定同一个数据;下拉菜单忘记给 option 写 value;单个复选框误用数组;一组复选框误用布尔值;把页面显示文字当成收集值。记住:输入类看 value,选中类看 checked,一组多选看数组。
1. 为什么需要计算属性
为什么需要:礼物清单里已经有 list 数据,页面上还要显示“礼物总数”。如果直接在模板里写 list[0].num + list[1].num + list[2].num,一旦增删数据或数量变化,表达式就要跟着改,复用性差。
怎么想:总数不是用户直接输入的,而是由现有数据“算出来”的新值;它依赖 list,list 变了就应自动重算。这正是 computed 的用途。
怎么做:把求和逻辑写进 computed,页面像使用普通属性一样使用它。
易错点:不要把总数写死在 data 里;计算属性函数必须 return 计算结果。
2. 概念与语法
为什么需要:Vue 希望“数据变化,页面自动更新”。计算属性基于现有数据产生新属性,依赖的数据变化时会自动重新计算。
怎么想:computed 是配置项,和 data 同级;一个计算属性对应一个函数;函数返回计算后的值;模板中用 {{ 计算属性名 }}。
怎么做:
js
computed: {
total() {
let sum = 0
this.list.forEach(item => {
sum += item.num
})
return sum
}
}易错点:计算属性虽然写成函数,但使用时是属性,模板中不能加小括号;不能和 data 中的属性同名;内部 this 指向 Vue 实例,所以用 this.list。
3. 注意点
为什么需要:computed 写错位置或用法,页面不会报“语法错误”,但结果会不对。
怎么想:把计算属性理解成“只读属性”:声明在 computed 中,使用在模板中。
怎么做:记住五条:与 data 同级;函数写法但仍是属性;不能与 data 同名;用法和普通属性一样;内部 this 指向 Vue 实例。
易错点:写进 methods 或 data;模板写成 {{ total() }};忘记 return。
4. 案例:礼物清单求和
比如我们可以使用计算属性实现下面这个业务场景:

先保留笔记中的代码准备:
html
<style>
table {
border: 1px solid #000;
text-align: center;
width: 240px;
}
th,td {
border: 1px solid #000;
}
h3 {
position: relative;
}
</style>
<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>礼物总数:? 个</p>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2/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 },
]
}
})
</script>怎么做:在 data 后面加逗号,添加 computed,并把模板中的 ? 换成 {{ total }}。
js
computed: {
total() {
let sum = 0
this.list.forEach(item => {
sum += item.num
})
return sum
}
}html
<p>礼物总数:{{ total }} 个</p>调试过程:刷新页面后总数应为 1+2+5=8。在控制台把某个 num 调大,页面总数会自动变化;在 total 中写 console.log('计算属性执行了'),模板里多次使用 {{ total }},控制台只打印一次,说明计算属性有缓存。
易错点:forEach 中累加要写 sum += item.num;最后必须 return sum。
5. 与 methods 对比
为什么需要:同样的求和也能用 methods 写,但两者执行时机不同。
怎么想:computed 有缓存:依赖不变时,多次使用只执行一次;methods 没有缓存:模板中调用几次就执行几次。
怎么做:computed 在模板中写 {{ total }};methods 在模板中写 {{ test() }}。
易错点:computed 不加括号,methods 调用要加括号。
6. 需要修改计算属性时:完整写法
为什么需要:计算属性默认只读,直接给它赋值会报错;有些场景需要反向修改。
怎么想:把计算属性写成对象,get 负责计算返回,set 接收修改后的值。
怎么做:例如用姓和名拼全名,修改全名时在 set(val) 中拆分并回写姓、名。
易错点:不需要修改时用函数写法即可;需要修改时再用 get/set;set 的形参是修改后的值。
一、computed计算属性:把数据加工成结果
为什么需要: 页面里经常要展示“由现有数据算出来的结果”,例如礼物清单中把每条 num 加起来得到礼物总数。如果每次都在模板里临时算,逻辑会散、可读性差,也不方便复用。
怎么想: 把“计算过程”封装成一个计算属性。它依赖 data 中的原数据,原数据变了,计算结果应自动更新。转写稿里强调:计算属性是“基于现有数据计算出来的一个新的属性”,依赖项变化会自动重新计算。
怎么做: 写在 computed 配置项中,和 data、methods 平级;计算属性本质是一个函数,必须 return 计算结果;模板中像普通属性一样使用,不加小括号。例如:
js
computed: {
totalCount () {
let total = this.list.reduce((sum, item) => sum + item.num, 0)
return total
}
}模板中写 {{ totalCount }},JS 中写 this.totalCount。
易错点: ① 忘记 return,计算结果为 undefined;② 模板中写成 {{ totalCount() }},这就变成调用方法而不是使用计算属性;③ 把 computed 写到 data 或 methods 里面,位置不对;④ 转写稿中提到的 reduce、forEach 等同音字要写准确,this.list 不要写成 list 漏掉 this。
二、methods方法:处理业务逻辑与事件
为什么需要: 除了“算出一个值”,开发中还有大量业务动作,例如按钮点击、删除、提交、事件处理。这些更适合写成方法,由调用者决定何时执行。
怎么想: methods 是给 Vue 实例提供方法,方法要被调用才执行。模板中调用方法要加小括号:{{ 方法名() }};事件绑定写 @事件名="方法名" 或 @事件名="方法名()"。JS 中调用写 this.方法名()。
怎么做: 写在 methods 配置项中,和 data、computed 平级。转写稿中对比测试时,在 methods 里写 test() 返回同样的计算结果,模板里调用 test(),可以算出相同结果。
易错点: ① 方法名调用时漏写小括号,模板不会执行方法;② 把“计算属性”和“方法”的使用语法混用:计算属性不加括号,方法要加括号;③ 方法没有缓存,页面中调用几次就执行几次,不要误以为它和计算属性一样只算一次;④ 转写稿里的 methods 不要写成 mes 等简写。
三、计算属性的优势:缓存特性与代码对比
为什么需要: 如果同一计算结果在页面中多次使用,每次都重新执行计算会浪费性能。计算属性有缓存特性:第一次计算后缓存结果,再次使用直接读缓存;依赖项变化才自动重新计算并再次缓存。methods 没有缓存,调用一次执行一次。
怎么想: 转写稿的调试过程是:在计算属性方法中输出“计算属性方法执行了”,页面中两次使用 totalCount,刷新后只输出一次;再在模板中多次使用,仍然只执行一次。换成 methods 的 test(),页面调用两次就输出两次。这说明“多次使用同一结果”时计算属性更有优势。
怎么做: 笔记中的对比例子如下,保留原代码:
html
<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>
<div id="app">
<h3>小黑的礼物清单🛒<span>?</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@2/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 () {
let total = this.list.reduce((sum, item) => sum + item.num, 0)
return total
}
}
})
</script>易错点: ① 认为 methods 也有缓存;② 在计算属性里修改原数据或产生副作用,计算属性应聚焦“算结果”;③ 依赖项变化后忘记它会自动重算,手动再赋值反而容易出错;④ 模板中计算属性误加括号。
四、总结:什么时候用computed,什么时候用methods
为什么需要: 两者都能算出结果,但定位不同,选错会让代码难维护。
怎么想: 转写稿和笔记的结论一致:computed 有缓存,methods 没有缓存;当一个结果依赖其他多个值时,推荐计算属性;当处理业务逻辑、事件处理函数时,推荐 methods。
怎么做: 礼物总数、购物车总价、成绩统计等“派生数据”用 computed;点击删除、提交表单、修改状态等“动作”用 methods。计算属性必须 return,模板中不加括号;方法调用要加括号。
易错点: 不要把事件处理函数写成计算属性;也不要把纯计算结果写成方法后在模板里反复调用,除非你明确不需要缓存。
为什么简写只能读不能改

为什么需要: 计算属性也是属性,页面里能像普通数据一样访问它,于是很容易产生一个疑问:既然能访问,能不能直接修改?笔记里明确说:计算属性默认的简写只能读取访问,不能“修改”。
怎么想: 简写形式本质上只提供了“计算并返回结果”的能力,相当于只写了 getter。你直接给计算属性赋值时,Vue 找不到对应的 setter,就会报错。
怎么做: 先保留笔记中的原始结构,观察简写写法:
html
<div id="app">
姓:<input type="text" v-model="firstName"> +
名:<input type="text" v-model="lastName"> =
<span></span><br><br>
<button>改名卡</button>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<script>
const app = new Vue({
el: '#app',
data: {
firstName: '刘',
lastName: '备'
},
computed: {
},
methods: {
}
})
</script>易错点: 不要把“能读取计算属性”误解成“能随意赋值”。简写适合只读场景;一旦涉及修改,就要换完整写法。
完整写法的 get 与 set
为什么需要: 有些业务确实需要“改计算属性”,例如把“姓 + 名”拼成完整姓名后,用户直接修改完整姓名,再反推更新姓和名。
怎么想: 完整写法要把计算属性写成对象,里面提供 get 和 set。get 负责计算并返回结果;set 在计算属性被直接修改时执行,形参表示修改后的结果。
怎么做: 语法结构是:
js
computed: {
fullName: {
get() {
return this.firstName + '.' + this.lastName
},
set(val) {
const arr = val.split('.')
this.firstName = arr[0]
this.lastName = arr[1]
}
}
}页面中把展示位置改成可修改的输入框,用 v-model 绑定 fullName,就能触发 set:
html
<div id="app">
姓:<input type="text" v-model="firstName"> +
名:<input type="text" v-model="lastName"> =
<input type="text" v-model="fullName"><br><br>
<button>改名卡</button>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<script>
const app = new Vue({
el: '#app',
data: {
firstName: '刘',
lastName: '备'
},
computed: {
fullName: {
get() {
return this.firstName + '.' + this.lastName
},
set(val) {
const arr = val.split('.')
this.firstName = arr[0]
this.lastName = arr[1]
}
}
},
methods: {}
})
</script>易错点: get 必须返回计算结果;set 的形参不要漏写。转写稿中反复强调:get 和以前简写函数一样,负责算结果;set 只在“直接修改计算属性”时执行。
调试过程:从报错到可修改
为什么需要: 只看语法容易忘,必须通过“先改、报错、再补 set”的过程理解完整写法存在的意义。
怎么想: 先用 v-model="fullName" 去改计算属性,控制台会提示没有 setter;补上对象写法和 set 后,再改输入框,set 就会执行。
怎么做: 在 set(val) 中先打印 val,输入“尼古拉斯.赵四”后失焦,可以看到形参就是修改后的完整值;再用 split('.') 拆成数组,分别赋给 firstName 和 lastName。这样页面上的姓、名也会同步变化。
易错点: 分隔符要统一。get 里用点拼接,set 里就按点拆分;如果输入中没有分隔符,arr[1] 可能是 undefined,实际案例要加判空或约定格式。不要写成 get 用点、set 用空格,否则拆分结果会乱。
案例目标与整体思路

本案例把前面学过的 v-for、动态 class、v-on、v-model 修饰符和 computed 串起来,完成成绩列表的渲染、删除、添加以及总分/平均分统计。
为什么需要:单个知识点会写,不代表能组合成完整页面;综合案例训练的是“数据驱动视图”的完整链路。
怎么想:先让列表按数据渲染出来,再给每行加删除,再用表单收集新数据,最后用计算属性根据 list 自动算总分和平均分。
怎么做:模板负责展示和绑定事件,methods 负责增删,computed 负责统计。
易错点:不要一边渲染一边手动改 DOM;删除链接要阻止默认跳转;添加前要判空;计算属性在模板中不要加小括号。
渲染成绩列表
为什么需要:成绩数据在 list 中,页面要用表格逐行展示,并且低于 60 分显示红色。
怎么想:用 v-for 循环 <tr>,用 :key 绑定唯一 id;分数单元格用动态 class,条件为 item.score < 60。转写稿中调试时把 20 分加到 60,红色消失;减到 59,又变红,说明动态类名由分数条件控制。
怎么做:
html
<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="del(item.id)">删除</a></td>
</tr>
<tr v-if="list.length === 0">
<td colspan="4">暂无数据</td>
</tr>易错点::class 的对象键 red 对应样式类名;条件要返回布尔值。暂无数据和列表行要二选一,转写稿用 v-if="list.length === 0" 控制“暂无数据”,删空后它才出现。:key 优先用数据已有 id,不要随便用重复值。
删除成绩
为什么需要:用户需要移除某条成绩,删除后列表和统计结果都应自动变化。
怎么想:删除链接是 <a href="#">,点击会默认跳转,所以要用 v-on 绑定点击并阻止默认行为。笔记明确写“v-on 绑定事件,阻止 a 标签的默认行为”。
怎么做:模板写 @click.prevent="del(item.id)",方法中按 id 过滤:
js
methods: {
del(id) {
this.list = this.list.filter(item => item.id !== id)
}
}易错点:忘记 .prevent 会导致地址栏出现 # 或页面跳动;删除后如果 list 为空,“暂无数据”应自动显示;不要直接操作 DOM 删除行。
添加成绩
为什么需要:案例要能录入新成绩,并把输入内容加入列表。
怎么想:用 v-model 双向绑定表单;科目用 .trim 去掉首尾空格,分数用 .number 转数字;添加前判断数据是否为空,添加后清空文本框。
怎么做:
html
<input v-model.trim="form.subject" placeholder="科目">
<input v-model.number="form.score" placeholder="分数">
<button @click="add">添加</button>js
data() {
return {
list: [],
form: { subject: '', score: '' }
}
},
methods: {
add() {
if (!this.form.subject || this.form.score === '') return
this.list.push({
id: Date.now(),
subject: this.form.subject,
score: Number(this.form.score)
})
this.form.subject = ''
this.form.score = ''
}
}易错点:.number 不是万能判空,空字符串仍要判断;添加后忘记清空输入框;新增数据要补唯一 id,避免 :key 重复。
统计总分与平均分
为什么需要:总分和平均分依赖 list,增删后要自动重算,不能每次手动改。转写稿强调计算属性有缓存:多次使用只执行一次;而 methods 是调用一次执行一次。
怎么想:在 computed 中基于 list 累加分数,再除以长度得到平均分。
怎么做:
js
computed: {
total() {
let sum = 0
this.list.forEach(item => {
sum += Number(item.score)
})
return sum
},
average() {
return this.list.length ? (this.total / this.list.length).toFixed(2) : 0
}
}模板中用 {{ total }}、{{ average }}。易错点:计算属性在模板中不能加小括号,加了就变成调用方法;空数组求平均要避免除以 0;分数若是字符串要先转数字。
课件图示







代码示例
示例 1:v-model 在各类表单元素上的使用
html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>v-model 在其它表单元素</title>
<style>
textarea {
display: block;
width: 240px;
height: 100px;
margin: 10px 0;
}
</style>
</head>
<body>
<div id="app">
<h3>小黑学习网</h3>
姓名:
<input type="text" v-model="username">
<br><br>
是否单身:
<input type="checkbox" v-model="isSingle">
<br><br>
性别:
<input type="radio" name="sex" value="男" v-model="gender">男
<input type="radio" name="sex" value="女" v-model="gender">女
<br><br>
所在城市:
<select v-model="city">
<option value="北京">北京</option>
<option value="上海">上海</option>
<option value="成都">成都</option>
<option value="南京">南京</option>
</select>
<br><br>
自我描述:
<textarea v-model="desc"></textarea>
<button @click="register">立即注册</button>
<p>当前数据:{{ username }} / {{ isSingle }} / {{ gender }} / {{ city }} / {{ desc }}</p>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<script>
const app = new Vue({
el: '#app',
data: {
username: '小黑',
isSingle: true, // 单个复选框 → 布尔值
gender: '男', // 单选框 → 与某个 value 相同
city: '上海', // 下拉框 → 与选中 option 的 value 相同
desc: '一个爱学习的前端'
},
methods: {
register () {
console.log({
username: this.username,
isSingle: this.isSingle,
gender: this.gender,
city: this.city,
desc: this.desc
})
}
}
})
</script>
</body>
</html>关键行与易错点
v-model会根据控件类型自动选正确的方法:文本框、文本域、下拉框绑的是value,复选框、单选框绑的是checked。- 单选框必须同时具备
name(同组互斥)和value(提交给后台的数据),v-model绑定的数据值要和value之一保持一致,否则一个都不选中。 - 拿到的是
value,不是页面上给用户看的文字:男、女神这种显示文字与数据无关。 - 单个复选框绑布尔值,多个复选框组成一组时必须绑数组。
select的v-model写在<select>上,不是写在<option>上;option要写value,否则提交的是标签文字。
示例 2:computed 计算属性 vs methods 方法(缓存差异)
html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>computed vs methods</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>
<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>礼物总数(computed):{{ totalCount }} 个</p>
<p>礼物总数(computed 第二次用):{{ totalCount }} 个</p>
<p>礼物总数(methods):{{ getTotalCount() }} 个</p>
<p>礼物总数(methods 第二次调):{{ getTotalCount() }} 个</p>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2/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('计算属性执行了')
let total = this.list.reduce((sum, item) => sum + item.num, 0)
return total
}
},
methods: {
getTotalCount () {
console.log('方法执行了')
let total = this.list.reduce((sum, item) => sum + item.num, 0)
return total
}
}
})
</script>
</body>
</html>关键行与易错点
computed与data、methods是同级配置项,不要写到data里面去。- 计算属性是函数写法、属性用法:模板中写
{{ totalCount }},不能加小括号,加了就变成调用方法了;JS 中用this.totalCount。 - 打开控制台可见:页面里用了两次
totalCount,但"计算属性执行了"只打印一次——第一次算完就缓存,依赖项(list里的num)变了才重新计算。 methods每调用一次执行一次,没有缓存,所以模板里写{{ getTotalCount() }}必须带括号。- 计算属性内部
this依然指向 Vue 实例;计算属性名不能和 data 中的属性同名。 - 结论:结果依赖多个数据 → 用 computed;处理业务逻辑(事件处理函数)→ 用 methods。
示例 3:计算属性的完整写法(get / set)
html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>计算属性完整写法</title>
</head>
<body>
<div id="app">
姓:<input type="text" v-model="firstName"> +
名:<input type="text" v-model="lastName"> =
<span>{{ fullName }}</span><br><br>
全名(可修改):<input type="text" v-model.lazy="fullName"><br><br>
<button @click="changeName">改名卡</button>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<script>
const app = new Vue({
el: '#app',
data: {
firstName: '刘',
lastName: '备'
},
computed: {
fullName: {
// get:读取 fullName 时执行,和原来的简写函数一模一样
get () {
return this.firstName + ' ' + this.lastName
},
// set:直接修改 fullName 时执行,形参 value 是修改后的结果
set (value) {
console.log('set 收到:', value)
const arr = value.split(' ')
this.firstName = arr[0] || ''
this.lastName = arr[1] || ''
}
}
},
methods: {
changeName () {
// 直接"修改"计算属性,会触发上面的 set
this.fullName = '诸葛 亮'
}
}
})
</script>
</body>
</html>关键行与易错点
- 简写(只写一个函数)只能读取,不能修改;一旦用
v-model绑到计算属性上再输入内容,就会报Computed property "fullName" was assigned to but it has no setter错误。 - 完整写法是对象格式:
计算属性名: { get(){}, set(){} },有多个计算属性时继续逗号往后写,每个都是这个对象结构。 get里必须return计算结果;set的形参就是修改后的新值,一般要在里面反向去改它所依赖的 data(这里是firstName、lastName)。- 用
v-model.lazy是为了失焦/回车时才触发,否则每敲一个字符就进一次set,体验和数据都乱。 - 易错:
split出来的第二项可能是undefined,所以要|| ''兜底;set里写回 data 时注意别把this弄丢。 - 用不到修改就用简写函数,需要"被修改"才写 get/set 完整写法。
常见错误
单个复选框勾选后数据没变化,或一组复选框勾一个全勾上
- 现象:
<input type="checkbox" v-model="isAgree">点击无反应;三个"爱好"复选框勾任意一个,另外两个同时被选中。 - 原因:v-model 绑定的数据类型与控制类型不匹配。单个 checkbox 需要布尔值,一组 checkbox 必须绑定数组,且每个 checkbox 要写
value。 - 修法:单个用
data: { isAgree: false };一组用hobby: [],每个 input 加value="eat"之类的值。
- 现象:
单选框选中后拿到 null / "on",或初始值设了却不选中
- 现象:点"男",
sex变成"on"或null;data里写了sex: '男',页面两个都不选中。 - 原因:没写
value(浏览器默认 value 是"on");或 v-model 初始值与任何一个 radio 的 value 都不严格相等(含类型、空格差异);或同组没写相同name导致不互斥。 - 修法:每个 radio 显式写
value,同一组共用同一个 v-model 变量,初始值必须是某个 value 的原样拷贝。
- 现象:点"男",
下拉框选中后取到 undefined,或初始不选中
- 现象:选"北京",
address是undefined;设了address: '北京'页面却是空选。 - 原因:
<option>没写value;或 v-model 初始值与 option 的 value 类型不一致(数字1与字符串"1")。 - 修法:每个 option 显式写
value,v-model 的初始值与之一一对应、类型一致。
- 现象:选"北京",
计算属性忘记 return,页面显示 undefined
- 现象:
{{ total }}渲染为空白或undefined,控制台无报错。 - 原因:函数体用了
{}却没有return;或改写成箭头函数导致this不是 Vue 实例,this.list为 undefined。 - 修法:计算属性必须
return结果;保持total() { ... }普通函数写法,别用箭头函数。
- 现象:
直接给计算属性赋值报错
- 现象:输入框改不动,控制台报
Computed property "fullName" was assigned to but it has no setter。 - 原因:计算属性简写形式只有 getter,是只读的。
- 修法:改成对象写法
{ get(){ return ... }, set(val){ ... } },在 set 里把val拆解回它依赖的 data。
- 现象:输入框改不动,控制台报
computed 与 data 同名,或在 computed 里做异步
- 现象:报
The computed property "xxx" is already defined in data;或模板渲染出[object Promise]。 - 原因:computed 与 data 是同级配置项,名字不能重复;计算属性必须同步返回结果。
- 修法:改名;异步取数放
created或 methods,拿到结果后再赋值给 data,computed 只做同步派生。
- 现象:报
模板中 computed 加了括号、methods 忘了括号
- 现象:
{{ totalCount() }}报totalCount is not a function;{{ getTotal }}直接把函数体打印到页面。 - 原因:computed 是属性,methods 是方法,调用形式不同。
- 修法:computed 不加括号,methods 在插值里必须加括号(事件绑定写方法名除外)。
- 现象:
:style对象里属性名不加引号、数值不带单位- 现象:宽度设置不生效,或控制台报语法错误。
- 原因:
{ width: 100 }生成无单位的width: 100,浏览器直接丢弃;background-color含中划线,不是合法标识符。 - 修法:写成
{ width: '100px' }、{ 'background-color': 'red' }或驼峰backgroundColor。
练习
表单全收集:把笔记里的注册页补全,用 v-model 收集姓名、是否单身、性别、所在城市、自我描述五个字段,再加一组"爱好"多选(吃饭/睡觉/打豆豆)。点按钮把整个对象
console.log出来。- 验收:手动改 data 里任一字段,页面同步变化;反向在页面操作,data 同步变化;爱好的值是数组,且数组中存的是 value 而不是页面文字。
进度条:用两层 div 搭进度条,内层宽度只通过
:style对象绑定一个变量percent控制,不新增任何 class;四个按钮分别设 25/50/75/100,加 transition 让宽度平滑过渡。- 验收:按钮点击后宽度变化且有动画;全程只改一个变量,没有出现
.w25、.w50这类类名。
- 验收:按钮点击后宽度变化且有动画;全程只改一个变量,没有出现
computed vs methods 实测:礼物清单求总数,先用 methods 写一遍,再用 computed 写一遍,两处都加
console.log,页面上各调用三次。- 验收:刷新后 computed 的日志只出现一次,methods 的日志出现三次;改任一
num后,computed 重新计算且只算一次。
- 验收:刷新后 computed 的日志只出现一次,methods 的日志出现三次;改任一
计算属性完整写法:姓 + 名 → 全名,用 v-model 绑定全名输入框,修改全名后能拆回姓和名(用
split(' ')处理)。- 验收:不写 set 时修改全名控制台报无 setter 错误;补上 get/set 后修改全名不再报错,姓和名输入框同步更新。
成绩案例:渲染成绩列表,低于 60 分显示红色(
:class对象写法);列表为空时显示"暂无数据";用 computed 求总分与平均分。- 验收:把某个分数改成 60 时红色消失、改成 59 时变红;删空列表后只剩"暂无数据"一行;平均分保留两位小数,且列表为空时不出现
NaN。
- 验收:把某个分数改成 60 时红色消失、改成 59 时变红;删空列表后只剩"暂无数据"一行;平均分保留两位小数,且列表为空时不出现
原视频对照
| # | 视频 | 时长 |
|---|---|---|
| 1 | v-bind对style的控制(10) | 03:36 |
| 2 | 案例-修改进度条案例(10) | 07:02 |
| 3 | v-model应用于其他表单项(11-12) | 17:08 |
| 4 | 计算属性-基本语法(13-14) | 10:31 |
| 5 | 计算属性-和methods方法对比(15-16) | 06:04 |
| 6 | 计算属性-完整写法(17-18) | 13:43 |
| 7 | 案例-成绩列表渲染(19-21) | 06:13 |
小结
- v-model 的绑定类型由控件类型决定,不由你的喜好决定:文本类控件(input:text / textarea / select)绑字符串或数字,单个 checkbox 绑布尔,一组 checkbox 绑数组,radio 绑字符串。
- 页面上显示的文字和程序拿到的值可以完全无关:前者是给用户看的,后者是
value。一组控件的 value 必须逐一显式书写。 - 计算属性是属性而非方法:写在 computed 配置项、与 data 同级、模板中不加括号、不能与 data 同名、必须有 return、内部 this 指向实例。
- computed 的缓存以"依赖是否变化"为失效条件,与"被使用多少次"无关;methods 每次渲染都重新执行,这是二者唯一的性能分水岭。
- 选型口诀:一个结果由多个数据推导而来 → computed;一次动作的处理逻辑(点击、提交) → methods。
- 计算属性默认只读,要让它被 v-model 反向写入就必须写 get/set;set 的职责不是赋值给自身,而是把新值拆解回它依赖的源数据。
- 依赖必须是响应式的(data / props / 其他 computed),依赖非响应式数据时计算属性不会重新计算,页面会"卡住不动"。
- 针对连续变化的值(进度条百分比、条件类名),用
:style对象或:class对象比预先堆一百个类名更合适;带中划线的 CSS 属性名要加引号,值要带单位。