一.计算属性
基于现有的数据,计算出来的新属性。依赖的数据变化,自动重新计算
语法:
1.声明在computed配置项中,一个计算属性对应一个函数
2.使用起来和普通属性一样使用 {{ 计算属性名 }}
3.计算属性就是将一段求值的代码进行封装,所以最后要return结果
案例:计算礼物
javascript
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: "Microsoft YaHei", sans-serif;
}
#app {
width: 400px;
}
.gift-table {
width: 100%;
border-collapse: separate;
border-spacing: 6px;
border: 2px solid #555;
}
.gift-table th,
.gift-table td {
width: 50%;
height: 40px;
border: 2px solid #555;
text-align: center;
font-size: 18px;
color: #333;
}
.gift-table th {
font-weight: bold;
font-size: 20px;
}
.total {
margin-top: 20px;
font-size: 20px;
color: #333;
}
</style>
<body>
<div id="app">
<table class="gift-table">
<thead>
<tr>
<th>名字</th>
<th>数量</th>
</tr>
</thead>
<tbody>
<tr v-for="item in list">
<td>{{item.name}}</td>
<td>{{item.num}}个</td>
</tr>
</tbody>
</table>
<p class="total">礼物总数:{{ totalCount }} 个</p>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2.7.16/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(){
console.log(this.list);
let total = this.list.reduce((sum,item)=>sum+item.num,0)
return total
}
}
})
</script>
</body>

缓存特性(提升性能):
计算属性会对计算出来的结果缓存,再次使用直接读取缓存,依赖项变化了,会自动重新计
算并再次缓存
computed计算属性:
作用:封装了一段对于数据的处理,求得一个结果
语法:
1.写在computed 配置项中
2.作为属性,直接使用 this.计算属性{{ 计算属性 }}
methods方法:
作用:给实例提供一个方法,调用以处理业务逻辑
语法:
1.写在methods配置项中
2.作为方法,需要调用 this.方法名(){{ 方法名() }} @事件名="方法名"
计算属性默认的简写,只能读取访问,不能"修改";如果要"修改"需要写计算属性的完整写法
javascript
computed:{
计算属性名:{
get(){
一段代码逻辑(计算逻辑)
return 结果
},
set(修改的值){
一段代码逻辑(修改逻辑)
}
}
}
案例:修改改名卡
javascript
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: "Microsoft YaHei", sans-serif;
}
#app {
width: 420px;
margin: 50px auto;
}
.row {
display: flex;
align-items: center;
gap: 8px;
font-size: 22px;
color: #333;
}
.row input {
width: 60px;
height: 38px;
border: 1px solid #aaa;
border-radius: 4px;
background-color: #f5f5f5;
font-size: 20px;
text-align: center;
outline: none;
}
.row input:focus {
border-color: #666;
background-color: #fff;
}
.row .op {
margin: 0 4px;
}
.rename-btn {
margin-top: 30px;
width: 110px;
height: 44px;
border: 1px solid #999;
border-radius: 4px;
background-color: #eee;
font-size: 18px;
cursor: pointer;
}
.rename-btn:hover {
background-color: #e0e0e0;
}
</style>
<body>
<div id="app">
<div class="row">
<span>姓:</span>
<input type="text" v-model="firstName">
<span class="op">+</span>
<span>名:</span>
<input type="text" v-model="lastName">
<span class="op">=</span>
<span>{{ fullName }}</span>
</div>
<button class="rename-btn" @click="changeName">改名卡</button>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2.7.16/dist/vue.js"></script>
<script>
const app = new Vue({
el: '#app',
data: {
firstName:'张',
lastName:'三'
},
methods:{
changeName(){
this.fullName = '李四'
}
},
computed:{
fullName:{
get(){
return this.firstName + this.lastName
},
set(value){
this.firstName = value.slice(0,1)
this.lastName = value.slice(1)
}
}
}
})
</script>
</body>
案例:成绩单
javascript
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: "Microsoft YaHei", sans-serif;
}
#app {
display: flex;
gap: 60px;
align-items: flex-start;
padding: 40px;
}
.score-table {
width: 540px;
border-collapse: collapse;
font-size: 15px;
color: #333;
}
.score-table th,
.score-table td {
height: 42px;
border: 1px solid #ddd;
text-align: center;
}
.score-table th {
background-color: #f7f7f7;
font-weight: bold;
}
.score-table .empty {
height: 120px;
color: #999;
}
.score-table .red {
color: #e1251b;
font-weight: bold;
}
.score-table .del {
color: #6a5acd;
text-decoration: underline;
cursor: pointer;
}
.score-table tfoot td {
padding-left: 40px;
text-align: left;
}
.score-table tfoot td span {
display: inline-block;
width: 130px;
}
.form-item {
display: flex;
align-items: center;
margin-bottom: 16px;
font-size: 14px;
color: #333;
}
.form-item label {
width: 44px;
text-align: right;
margin-right: 8px;
}
.form-item input {
width: 190px;
height: 32px;
padding: 0 8px;
border: 1px solid #ddd;
outline: none;
font-size: 14px;
}
.form-item input:focus {
border-color: #1f6f96;
}
.add-btn {
margin-left: 52px;
padding: 6px 16px;
border: none;
border-radius: 3px;
background-color: #1f6f96;
color: #fff;
font-size: 14px;
cursor: pointer;
}
.add-btn:hover {
background-color: #2a86b4;
}
</style>
<body>
<div id="app">
<table class="score-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="#" class="del" @click="del(item.id)">删除</a></td>
</tr>
</tbody>
<tbody v-else>
<tr>
<td colspan="4" class="empty">暂无成绩记录</td>
</tr>
</tbody>
<tfoot>
<tr>
<td colspan="4">
<span>总分:{{ totalScore }}</span>
<span>平均分:{{ avgScore }}</span>
</td>
</tr>
</tfoot>
</table>
<div class="form">
<div class="form-item">
<label>科目:</label>
<input type="text" placeholder="请输入科目" v-model.trim="subject">
</div>
<div class="form-item">
<label>分数:</label>
<input type="text" placeholder="请输入分数" v-model.number="score">
</div>
<button class="add-btn" @click="add">添加</button>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2.7.16/dist/vue.js"></script>
<script>
const app = new Vue({
el: '#app',
data: {
list: [
{ id: 1, subject: '语文', score: 80 },
{ id: 2, subject: '数学', score: 90 },
{ id: 3, subject: '英语', score: 55 }
],
subject: '',
score: ''
},
methods:{
del(id){
this.list = this.list.filter(item => item.id !== id);
},
add(){
if(!this.subject || !this.score)return
this.list.push({
id: this.list.length + 1,
subject: this.subject,
score: this.score
});
this.subject = '';
this.score = '';
}
},
computed:{
totalScore(){
return this.list.reduce((sum, item) => sum + item.score, 0);
},
avgScore(){
return this.list.length > 0 ? (this.totalScore / this.list.length).toFixed(2) : 0;
}
}
})
</script>
</body>




二.watch监听器
作用:监视数据变化,执行一些业务逻辑或异步操作
语法:
1.简单写法 简单类型数据,直接监视
2.完整写法 添加额外配置项
deep:true 对复杂类型深度监视
immediate:true 初始化立刻执行一次handler方法
javascript
<body>
<div id="app">
<textarea name="" id="" cols="30" rows="10" v-model="msg"></textarea>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2.7.16/dist/vue.js"></script>
<script>
const app = new Vue({
el: '#app',
data: {
msg: ''
},
watch:{
msg(newValue,oldValue){
console.log('msg变化了',newValue,oldValue);
}
}
})
</script>
</body>

javascript
<body>
<div id="app">
<textarea name="" id="" cols="30" rows="10" v-model="msg"></textarea>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2.7.16/dist/vue.js"></script>
<script>
const app = new Vue({
el: '#app',
data: {
msg: ''
},
watch:{
msg(newValue){
console.log('msg变化了',newValue);
}
}
})
</script>
</body>

javascript
<body>
<div id="app">
<textarea name="" id="" cols="30" rows="10" v-model="obj.msg"></textarea>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2.7.16/dist/vue.js"></script>
<script>
const app = new Vue({
el: '#app',
data: {
obj:{
msg: ''
}
},
watch:{
// 监视对象子属性变化
'obj.msg'(newValue){
console.log('msg变化了',newValue);
}
}
})
</script>
</body>

案例:水果购物车(重点)
javascript
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: "Microsoft YaHei", sans-serif;
background-color: #f5f5f5;
padding: 30px 0;
}
#app {
width: 720px;
margin: 0 auto;
background-color: #fff;
}
.cart-header,
.cart-row {
display: flex;
align-items: center;
padding: 16px 12px;
border-bottom: 1px solid #eee;
}
.cart-header {
background-color: #fafafa;
font-size: 14px;
color: #666;
font-weight: bold;
}
.col-check { width: 40px; text-align: center; }
.col-img { width: 80px; text-align: center; }
.col-price { width: 100px; text-align: center; }
.col-count { width: 140px; text-align: center; }
.col-total { width: 100px; text-align: center; }
.col-ops { width: 80px; text-align: center; }
.col-img img {
width: 60px;
height: 60px;
border-radius: 4px;
object-fit: cover;
}
.count-box {
display: inline-flex;
align-items: center;
border: 1px solid #ddd;
border-radius: 3px;
}
.count-box .btn {
width: 28px;
height: 28px;
border: none;
background-color: #f5f5f5;
color: #555;
font-size: 16px;
cursor: pointer;
}
.count-box .btn:hover {
background-color: #e8e8e8;
}
.count-box .num {
width: 36px;
height: 28px;
line-height: 28px;
text-align: center;
font-size: 14px;
color: #333;
}
.del-btn {
border: none;
background-color: #e1251b;
color: #fff;
font-size: 13px;
padding: 5px 12px;
border-radius: 3px;
cursor: pointer;
}
.del-btn:hover {
background-color: #c0392b;
}
.cart-footer {
display: flex;
align-items: center;
justify-content: flex-end;
padding: 16px 12px;
gap: 16px;
}
.cart-footer .select-all {
margin-right: auto;
font-size: 14px;
color: #333;
display: flex;
align-items: center;
gap: 6px;
}
.cart-footer .total-price {
font-size: 16px;
color: #333;
}
.cart-footer .total-price b {
color: #e1251b;
font-size: 22px;
margin-left: 4px;
}
.cart-footer .checkout-btn {
width: 120px;
padding: 10px 28px;
border: none;
border-radius: 4px;
background-color: #4a9eff;
color: #fff;
font-size: 15px;
cursor: pointer;
}
.cart-footer .checkout-btn:hover {
background-color: #3a8eed;
}
.empty {
width: 100%;
height: 100px;
line-height: 120px;
text-align: center;
color: #999;
font-size: 18px;
}
.active{
background-color: #f5f7fb;
}
</style>
<body>
<div id="app">
<!-- 表头 -->
<div class="cart-header">
<div class="col-check">选中</div>
<div class="col-img">图片</div>
<div class="col-price">单价</div>
<div class="col-count">个数</div>
<div class="col-total">小计</div>
<div class="col-ops">操作</div>
</div>
<!-- 商品行 -->
<div v-if="fruitList.length > 0">
<div class="cart-row" :class="{ active:item.isChecked }" v-for="item in fruitList" :key="item.id">
<div class="col-check">
<input type="checkbox" v-model="item.isChecked">
</div>
<div class="col-img">
<img :src="item.icon" alt="">
</div>
<div class="col-price">{{item.price}}</div>
<div class="col-count">
<div class="count-box">
<button class="btn" @click="sub(item.id)" :disabled="item.count <= 1">-</button>
<span class="num">{{item.count}}</span>
<button class="btn" @click="add(item.id)">+</button>
</div>
</div>
<div class="col-total">{{item.price * item.count}}</div>
<div class="col-ops"><button class="del-btn" @click="del(item.id)">删除</button></div>
</div>
</div>
<div class="empty" v-else>暂无商品</div>
<!-- 底部结算栏 -->
<div class="cart-footer">
<label class="select-all">
<input type="checkbox" v-model="isAllChecked"> 全选
</label>
<span class="total-price">总价:<b>¥{{ totalPrice }}</b></span>
<button class="checkout-btn">结算({{ totalCount }})</button>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2.7.16/dist/vue.js"></script>
<script>
const fruitArr = [
{ id:1,icon:'./images/apple.png',isChecked:true,price:6,count:2},
{ id:2,icon:'./images/banana.png',isChecked:false,price:7,count:1},
{ id:3,icon:'./images/watermelon.png',isChecked:false,price:3,count:3}
]
const app = new Vue({
el: '#app',
data: {
fruitList: JSON.parse(localStorage.getItem('fruitList')) || fruitArr
},
computed:{
isAllChecked:{
get(){
return this.fruitList.every(item => item.isChecked)
},
set(value){
this.fruitList.forEach(item => item.isChecked = value)
}
},
totalPrice(){
return this.fruitList.reduce((sum,item) => {
if(item.isChecked){
return sum + item.price * item.count
}else{
return sum
}
},0)
},
totalCount(){
return this.fruitList.reduce((sum,item) => {
if(item.isChecked){
return sum + item.count
}else{
return sum
}
},0)
}
},
methods:{
del(id){
this.fruitList = this.fruitList.filter(item => item.id !== id)
},
sub(id){
const fruit = this.fruitList.find(item => item.id === id)
fruit.count--
},
add(id){
const fruit = this.fruitList.find(item => item.id === id)
fruit.count++
}
},
watch:{
fruitList:{
deep:true,
handler(newValue){
localStorage.setItem('fruitList',JSON.stringify(newValue))
}
}
}
})
</script>
</body>





