05-计算属性,侦听器
码路教育 6/3/2022
# 1, 计算属性computed
# 1.1, 复杂data的处理方式(计算属性)
在模板中可以直接通过插值语法显示一些复杂data中的数据
- 对多个data数据进行运算、三元运算符来决定结果、数据进行某种转化后显示
- 在模板中使用表达式,可以非常方便的实现,但是设计它们的初衷是用于简单的运算
- 在模板中放入太多的逻辑会让模板过重和难以维护
- 如果多个地方都使用到,那么会有大量重复的代码
如何解决模板中放复杂data
- 将逻辑抽取到一个method中,放到methods的options中
- 但是,这种做法有一个直观的弊端,就是所有的data使用过程都会变成了一个方法的调用
- 另外一种方式就是使用计算属性computed
什么是计算属性
- 官方并没有给出直接的概念解释,而是说:对于任何包含响应式数据的复杂逻辑,你都应该使用计算属性
- 计算属性将被混入到组件实例中
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<!-- 引入Vue.js,人家向外暴露一个Vue类 new Vue -->
<script src="../lib/vue2.7.8.js"></script>
</head>
<body>
<!-- 定义的容器 -->
<div id="app">
请输入你的姓:<input type="text" v-model="firstName"> <br>
请输入你的名:<input type="text" v-model="lastName"> <br>
<!-- vue官方不推荐在{{}}语法中做运算 -->
<!-- 如果有复杂运算,建议把复杂运算放到计算属性中 -->
<!-- <p>全名:{{ firstName + lastName }}</p> -->
<p>全名:{{ allName() }}</p>
<p>全名:{{ allName() }}</p>
<p>全名:{{ allName() }}</p>
<p>全名:{{ allName() }}</p>
<hr>
<!-- 计算属性写的时候,是函数的形式,用的时候是普通属性的形式 -->
<!-- 计算属性不能加()去调用 -->
<!-- 在模板中,第1次使用计算属性时,会执行计算属性对应的函数 -->
<!-- 后面如果计算属性依赖的数据没有发生变化,计算属性会缓存起来 -->
<p>全名:{{ allName2 }}</p>
<!-- 下面使用计算属性,都是从缓存中取出来的属性,性能高 -->
<p>全名:{{ allName2 }}</p>
<p>全名:{{ allName2 }}</p>
<p>全名:{{ allName2 }}</p>
</div>
<script>
let vm = new Vue({
el:"#app",
data(){
return {
firstName:"wang",
lastName:"Cai"
}
},
methods:{
// 把复杂运算放到方法中,也是OK的
allName(){
console.log("我是方法....");
return this.firstName + this.lastName
}
},
computed:{
// 计算属性:根据已有的状态,计算出一个新的状态
// 状态 ==> 数据
// 最常见的形式,也是写成函数的形式
// 但是实际上是一个属性值
allName2(){
console.log("我是计算属性....");
// 计算属性的值,取决于函数的返回值
// return 123;
// 当计算属性依赖的数据变化了,计算属性会重新计算
// return this.firstName + this.lastName;
// 在计算属性中尝试写异步代码?
// 答:计算属性不能写异步代码。如:定时器,ajax,事件绑定...
// setTimeout(()=>{
// return this.firstName + this.lastName;
// },2000)
// 在计算属性中,this表示vm
// console.log(this);
return this.firstName + this.lastName;
}
}
});
console.log(vm);
</script>
</body>
</html>
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
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
计算属性的完整写法
- 计算属性还有另外一种写法:第一种写法->函数写法
- 计算属性还有第二种写法:第二种写法->对象
- 注意:计算属性,一般都是简写方式,很少使用完成写法!!!
- 注意:在书写项目的时候,计算属性一般简写方式!!!
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<!-- 引入Vue.js,人家向外暴露一个Vue类 new Vue -->
<script src="../lib/vue2.7.8.js"></script>
</head>
<body>
<!-- 定义的容器 -->
<div id="app">
请输入你的姓:<input type="text" v-model="firstName"> <br>
请输入你的名:<input type="text" v-model="lastName"> <br>
<p>全名:{{ allName }}</p>
<button @click="updateName">修改名字为:李-四</button>
</div>
<script>
let vm = new Vue({
el:"#app",
data(){
return {
firstName:"Wang",
lastName:"Cai"
}
},
methods:{
updateName(){
// 计算属性allName也会挂载到vm上的
this.allName = "李-四"
}
},
computed:{
// allName(){
// return this.firstName + this.lastName;
// }
// 计算属性的另一种写法(用的不多) 对象的形式
allName:{
// 当在模板中使用计算属性时,会自动走get
get(){
return this.firstName + "-" + this.lastName;
},
// 当修改计算属性时,会走set,但是一般情况下,不会修改计算属性
set(val){
// val是计算属性的新值
// console.log("set...");
// console.log(val);
let arr = val.split("-")
this.firstName = arr[0]
this.lastName = arr[1]
}
}
}
});
</script>
</body>
</html>
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
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
购物车案例
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<!-- 引入Vue.js,人家向外暴露一个Vue类 new Vue -->
<script src="../lib/vue2.7.8.js"></script>
<style>
* {
margin: 0;
padding: 0;
}
table,
tr,
td {
border: 1px solid skyblue;
border-collapse: collapse;
}
td {
width: 150px;
height: 50px;
text-align: center;
line-height: 50px;
}
</style>
</head>
<body>
<!-- 定义的容器 -->
<div id="app">
<!-- 使用表格制作购物车 -->
<table>
<tr>
<th>商品名称</th>
<th>单价</th>
<th>数量</th>
<th>总价</th>
</tr>
<tr v-for="(item,index) in cart">
<td>{{item.title}}</td>
<td>{{item.price}}</td>
<td>
<button @click="minus(item)">减一</button>
{{item.count}}
<button @click="add(item)">加一</button>
</td>
<td>{{ item.price * item.count }}</td>
</tr>
</table>
<h2>所有商品总价:{{total}}</h2>
</div>
<script>
let vm = new Vue({
el:"#app",
data(){
return {
cart: [{
id: 1,
title: "手机",
price: 1999,
count: 1
},
{
id: 2,
title: "电脑",
price: 9992,
count: 1
},
{
id: 3,
title: "衣服",
price: 29,
count: 1
},
{
id: 4,
title: "洗衣机",
price: 5321,
count: 1
},
{
id: 5,
title: "篮球",
price: 19,
count: 1
},
{
id: 6,
title: "球杆",
price: 2999,
count: 1
},
]
}
},
computed:{
total(){
// 得到购物车中所有商品的单价*数量
// 第一种方式
// 遍历
// let sum = 0;
// for(let i=0; i<this.cart.length; i++){
// sum += this.cart[i].price * this.cart[i].count;
// }
// return sum;
// 第二种方式
// let sum = 0;
// this.cart.forEach(item=>{
// sum += item.price * item.count;
// })
// return sum;
// 第三种方式
return this.cart.reduce((prev,next)=>prev+next.price*next.count,0)
}
},
methods:{
minus(item){
if(item.count > 1){
item.count -= 1;
}
},
add(item){
item.count += 1;
},
}
});
</script>
</body>
</html>
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
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
# 1.2, 计算属性 vs methods
- methods它主要的作用是给VM实例添加方法----方法
- computed:它是利用已有的属性与属性值创建出一个新的属性与属性值----属性
- 区别1: methods方法在使用的时候一般需要加上小括号, 计算出来的属性, 在使用的时候是不需要加小括号的
- 区别2:计算属性算出来的数值有缓存机制, 计算出一个可以多次使用
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<!-- 引入Vue.js,人家向外暴露一个Vue类 new Vue -->
<script src="../lib/vue2.7.8.js"></script>
</head>
<body>
<!-- 定义的容器 -->
<div id="app">
<h2>{{ showMsg() }}</h2>
<h2>{{ showMsg() }}</h2>
<h2>{{ showMsg() }}</h2>
<h2>{{ showMsg() }}</h2>
<h2>{{ showMsg() }}</h2>
<hr>
<h2>{{ showTotal }}</h2>
<h2>{{ showTotal }}</h2>
<h2>{{ showTotal }}</h2>
<h2>{{ showTotal }}</h2>
<h2>{{ showTotal }}</h2>
</div>
<script>
let vm = new Vue({
el:"#app",
data(){
return {
}
},
methods:{
showMsg(){
console.log("我是方法");
return "method"
}
},
computed:{
showTotal(){
console.log("我是计算属性");
return "computed"
}
}
});
console.log(vm);
</script>
</body>
</html>
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
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
# 2, 侦听器watch
# 2.1, 什么是侦听器
开发中我们在data返回的对象中定义了数据,这个数据通过插值语法等方式绑定到template中, 当数据变化时,template会自动进行更新来显示最新的数据, 但是在某些情况下,我们希望在代码逻辑中监听某个数据的变化,这个时候就需要用侦听器watch来完成了
监听VM响应式属性写法 ,不管是对象、函数写法都常用
- 第一种函数写法
- 第二种对象写法
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<!-- 引入Vue.js,人家向外暴露一个Vue类 new Vue -->
<script src="../lib/vue2.7.8.js"></script>
</head>
<body>
<!-- 定义的容器 -->
<div id="app">
请输入搜索的关键字:<input type="text" placeholder="关键字" v-model="keyword">
</div>
<!--
开发中我们在data返回的对象中定义了数据,这个数据通过插值语法等方式绑定到template中, 当数据变化时,template会自动进行更新来显示最新的数据, 但是在某些情况下,我们希望在代码逻辑中监听某个数据的变化,这个时候就需要用侦听器watch来完成了 -->
<script>
let vm = new Vue({
el:"#app",
data(){
return {
keyword:""
}
},
watch:{
// 表示侦听keyword 如果keyword发生了变化 就可以被侦听到
// 函数的写法
// keyword(){
// console.log("我侦听到了keyword的变化");
// }
// 对象的写法
keyword:{
// 对象的写法,需要在对象中写一个handler
// 当keyword发生了变化,会自动执行handler
// hander名字不能随便写
handler(){
console.log("我侦听到了keyword的变化");
}
}
}
});
</script>
</body>
</html>
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
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
# 2.2, 侦听器watch的配置选项
默认情况下,watch只是在侦听第1层数据,对于内部属性的变化是不会做出响应的
- 这个时候我们可以使用一个选项deep进行更深层的侦听
- 注意前面我们说过watch里面侦听的属性对应的也可以是一个Object
- watch监听, 监听的是VM身上响应式属性的属性值的变化->VM身上的属性
另外一个属性,是希望一开始的就会立即执行一次
- 使用immediate选项
- 无论后面数据是否有变化,侦听的函数都会有限执行一次
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<!-- 引入Vue.js,人家向外暴露一个Vue类 new Vue -->
<script src="../lib/vue2.7.8.js"></script>
</head>
<body>
<!-- 定义的容器 -->
<div id="app">
<button @click="a.b.count++">+</button>
<span>{{a.b.count}}</span>
<button @click="a.b.count--">-</button>
<h3>{{a.b.msg}}</h3>
</div>
<script>
let vm = new Vue({
el: "#app",
data() {
return {
a: {
b: {
count: 1,
msg: '我爱你'
}
}
}
},
watch:{
// 侦听a这个响应式数据 函数的写法
// a(){
// console.log("i love u");
// }
// 如果想侦听到对象中所有的属性,需要写成对象的形式
a:{
deep:true, // 尝试侦听
immediate:true, // 一上来,先执行一次侦听器
handler(){
// console.log("i love u");
// 在侦听器中可以写异步代码
setTimeout(()=>{
this.a.b.msg = "lalala"
},3000)
}
}
}
});
</script>
</body>
</html>
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
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
# 3, 案例练习
# 2.1, 百度搜索实战案例
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<!-- 引入Vue.js,人家向外暴露一个Vue类 new Vue -->
<script src="../lib/vue2.7.8.js"></script>
</head>
<body>
<!-- 定义的容器 -->
<div id="app">
请你输入搜索的关键字: <input type="text" v-model="keyword.a.b">
<ul>
<li v-for="(item,index) in arr">
{{item.q}}
</li>
</ul>
</div>
<script>
// https://www.baidu.com/sugrec?pre=1&p=3&ie=utf-8&json=1&prod=pc&from=pc_web&wd=nba&req=2&csor=1&cb=cb&_=1648605785333
let vm = new Vue({
el:"#app",
data(){
return {
keyword:{
a:{
b:"vue"
}
},
arr:[]
}
},
watch:{
keyword:{
deep:true,
immediate:true,
handler(){
let that = this;
window.cb = function(data){
console.log("data:",data.g);
// this.arr = data.g
that.arr = data.g;
}
let script = document.createElement("script");
script.src = `https://www.baidu.com/sugrec?pre=1&p=3&ie=utf-8&json=1&prod=pc&from=pc_web&wd=${this.keyword.a.b}&req=2&csor=1&cb=cb&_=1648605785333`;
document.body.prepend(script);
}
}
}
});
</script>
</body>
</html>
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
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
# 2.2, 购物车实战案例
const books = [{
id: 1,
name: '《精通Vue》',
date: '2006-9',
price: 85.00,
count: 1
},
{
id: 2,
name: '《小程序实战教程》',
date: '2006-2',
price: 59.00,
count: 1
},
{
id: 3,
name: '《React从入门到精通》',
date: '2008-10',
price: 39.00,
count: 1
},
{
id: 4,
name: '《算法之美》',
date: '2006-3',
price: 128.00,
count: 1
},
{
id: 5,
name: '《图解HTTP》',
date: '2014-8',
price: 88.00,
count: 1
},
]
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
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
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<!-- 引入Vue.js,人家向外暴露一个Vue类 new Vue -->
<script src="../lib/vue2.7.8.js"></script>
<script src="./data/data.js"></script>
<style>
table {
border-collapse: collapse;
/* text-align: center; */
}
thead {
background-color: #f5f5f5;
}
th,
td {
border: 1px solid #aaa;
padding: 8px 16px;
}
.active {
background-color: skyblue;
}
</style>
</head>
<body>
<!-- 定义的容器 -->
<div id="app">
<template v-if="books.length">
<table>
<thead>
<tr>
<td>序号</td>
<td>书籍名称</td>
<td>出版日期</td>
<td>价格</td>
<td>购物数量</td>
<td>操作</td>
</tr>
</thead>
<tbody>
<tr v-for="(item,index) in books" :class="{active:index === currentIndex}" @click="rowClick(index)">
<td>{{ index + 1 }}</td>
<td>{{ item.name }}</td>
<td>{{ item.date }}</td>
<td>{{ formatPrice(item.price) }}</td>
<td>
<button :disabled="item.count<=1" @click.stop="decrement(index,item)">-</button>
{{item.count}}
<button @click.stop="increment(index,item)">+</button>
</td>
<td>
<button @click.stop="removeBook(index,item)">删除</button>
</td>
</tr>
</tbody>
</table>
<h2>总价:{{ formatPrice(totalPrice) }}</h2>
</template>
<template v-else>
<h1>购物车空空如也,请添加你喜欢图书!</h1>
</template>
</div>
<script>
let vm = new Vue({
el:"#app",
data(){
return {
books:books,
currentIndex:-1
}
},
computed:{
totalPrice(){
return this.books.reduce((prev,next)=>prev + next.price*next.count,0)
}
},
methods:{
formatPrice(price){
return "¥"+price
},
decrement(index,item){
item.count--
},
increment(index,item){
item.count++
},
removeBook(index,item){
this.books.splice(index,1)
},
rowClick(index){
this.currentIndex = index;
}
}
});
</script>
</body>
</html>
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
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