Data、Vue方法、计算属性及监听器

  1. 1. 一、Data函数
  2. 2. 二、methods方法
  3. 3. 三、 computed监听
  4. 4. 四、methods方法与computed计算属性的区别
  5. 5. 五、watch 监听器
    1. 5.1. 5.1 浅度监听
    2. 5.2. 5.2 深度监听
      1. 5.2.1. 5.2.1 错误示例
      2. 5.2.2. 5.2.2 解决方案

一、Data函数

该函数返回组件实例的 data 对象。Vue 会在创建新组件实例的过程中调用此函数。它应该返回一个对象,然后 Vue 会通过响应性系统将其包裹起来,并以 $data 的形式存储在组件实例中。为方便起见,该对象的任何顶级 property 也会直接通过组件实例暴露出来。

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
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
<script src="js/vue3.js" type="text/javascript" charset="utf-8"></script>
</head>
<body>
<div id="app">
{{count}}
</div>
<script type="text/javascript">
// 这个对象将添加到组件实例中
const app = Vue.createApp({
data() {
return { count: 4 }
}
})

const vm = app.mount('#app')

console.log(vm.$data.count) // => 4
console.log(vm.count) // => 4

// 修改 vm.count 的值也会更新 $data.count
vm.count = 5
console.log(vm.$data.count) // => 5

// 反之亦然
vm.$data.count = 6
console.log(vm.count) // => 6
</script>

</body>
</html>

实例创建之后,可以通过 vm.$data 访问原始数据对象。组件实例也代理了 data 对象上所有的 property,因此访问 vm.a 等价于访问 vm.$data.a

注:以 _$ 开头的 property 不会被组件实例代理,因为它们可能和 Vue 内置的 property、API 方法冲突。你可以使用例如 vm.$data._property 的方式访问这些 property。

二、methods方法

我们用 methods选项向组件实例添加方法,这个methods方法是一个包含我们所需方法的对象:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<div id="app">
<button v-on:click="add">Dianji</button>
{{cnt}}
</div>
<script type="text/javascript">
const vm = {
data() {
return {
cnt: 1
}
},
methods: {
add() {
// `this` 指向该组件实例
this.cnt = this.cnt + 1
}
}
}
Vue.createApp(vm).mount('#app')
</script>

Vue 自动为 methods 绑定 this,以便于它始终指向组件实例。这将确保方法在用作事件监听或回调时保持正确的 this指向。

在定义 methods 时应避免使用箭头函数,因为这会阻止 Vue 绑定恰当的 this 指向。

三、 computed监听

计算属性的结果会被缓存,只有当依赖的响应式 property 变化时才会重新计算。注意,如果某个依赖 (比如非响应式 property) 在该实例范畴之外,则计算属性是不会被更新的。

3.1 计算属性是根据依赖关系进行缓存的计算,并且只在需要的时候进行更新。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<div id="app">
<p>原数据:{{msg}}</p>
<p>新数据: {{reversedMsg}}</p>
</div>

<script type="text/javascript">
const vm = {
data() {
return {
msg: 'hello world!'
}
},
computed: {
reversedMsg() {
return this.msg.split('').reverse().join('');
}
}
}
Vue.createApp(vm).mount('#app')

3.2 对数据进行过滤处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<div id="app">
{{moneyStr}}
</div>

<script src="https://unpkg.com/vue@next"></script>
<script>
Vue.createApp({
data() {
return {
money: 100
}
},
computed:{
moneyStr(){
return this.money + '¥'
}
}
}).mount('#app')
</script>

四、methods方法与computed计算属性的区别

虽然使用computed和methods方法两种方法实现一个功能的结果是相同的,但本质是不一样的。

计算属性是基于它们的依赖进行缓存的。计算属性只有在它的相关依赖发生改变的时候才会重新求值,这就意味着只要message还没有发生改变,多次访问reversedMessage计算属性立即返回的是之前计算的结果,而不会再次执行计算函数。

而对于methods方法,只要发生重新渲染,methods调用总会执行该函数。

如果某个computed需要的遍历一个极大的数组和做大量的计算,可以减小性能开销,如果不希望有缓存,则用methods。

五、watch 监听器

watch能够监听数据的改变。监听之后会调用一个回调函数。
此回调函数的参数有两个:

  1. 更新后的值(新值)

  2. 更新前的值(旧值)

5.1 浅度监听

下面使用watch来监听商品数量的变化。如果商品数量小于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
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
<div id="app">
<table width="100%" style="text-align: center;">
<tr>
<th>商品编号</th>
<th>商品名称</th>
<th>商品单价</th>
<th>商品数量</th>
<th>合计</th>
</tr>
<tr>
<td>1</td>
<td>小米10</td>
<td>{{price}}</td>
<td>
<button @click="subtract">-</button>
{{quantity}}
<button @click="add">+</button>
</td>
<td>{{totalPrice}}</td>
</tr>
</table>
</div>

<script type="text/javascript">
Vue.createApp({
data() {
return {
price: 2999,
quantity: 1
}
},
computed: {
totalPrice() {
return this.price*this.quantity
}
},
methods: {
add() {
this.quantity++;
},
subtract() {
this.quantity--;
}
},
watch: {
quantity(newVal, oldVal) {
console.log(newVal, oldVal);
this.quantity = newVal <= 0 ? oldVal : newVal
}
}
}).mount('#app')
</script>

5.2 深度监听

在上面的例子中,监听的简单的数据类型,数据改变很容易观察,但是当需要监听的数据变为对象类型的时候,上面的监听方法就失效了,因为上面的简单数据类型属于浅度监听,对应的对象类型就需要用到深度监听,只需要在上面的基础上加上deep: true就可以了。

5.2.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
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
<div id="app">
<table width="100%" style="text-align: center;">
<tr>
<th>商品编号</th>
<th>商品名称</th>
<th>商品单价</th>
<th>商品数量</th>
<th>合计</th>
</tr>
<tr>
<td>1</td>
<td>小米10</td>
<td>{{goods.price}}</td>
<td>
<button @click="subtract">-</button>
{{goods.quantity}}
<button @click="add">+</button>
</td>
<td>{{totalPrice}}</td>
</tr>
</table>

</div>

<script type="text/javascript">
Vue.createApp({
data() {
return {
goods: {
price: 2999,
quantity: 1
}
}
},
computed: {
totalPrice() {
return this.goods.price * this.goods.quantity;
}
},
methods: {
add() {
this.goods.quantity++;
},
subtract() {
this.goods.quantity--;
}
},
watch: {
goods: {
handler(newVal, oldVal) {
console.log(newVal, oldVal);
this.goods.quantity = newVal.quantity <= 0 ? oldVal.quantity : newVal.quantity;
},
deep: true
}
}
}).mount('#app')
</script>
  • 上面代码中,由于监听的是对象类型,所以 newVal 与 oldVal 都指向同一个对象。

  • 所以,在深度监听对象时,是不能正确获取更新前的对象和更新后的对象的,所以会出现数量为负数的情况

5.2.2 解决方案

利用计算属性将对象变成字符串后再监听。

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
<div id="app">
商品名称:{{goods.name}}; 数量:
<button @click="sub">-</button>{{goods.quantity}}<button @click="add">+</button>
<br>
总价:{{total}}
</div>

<script>
Vue.createApp({
data() {
return {
goods: {
name: '小米手机',
quantity: 1
}
}
},
computed: {
total() {
return this.goods.quantity * 100;
},
goodsStr() {
return JSON.stringify(this.goods);
}
},
methods: {
add() {
this.goods.quantity++;
},
sub() {
this.goods.quantity--;
}
},
watch: {
goodsStr(newVal, oldVal) {
newGoods = JSON.parse(newVal);
oldGoods = JSON.parse(oldVal);
this.goods.quantity = newGoods.quantity<=0?oldGoods.quantity:newGoods.quantity;

}
}
}).mount('#app');
</script>