# 计算属性和侦听器
# 计算“属性”
# 缘由
模板内的表达式非常便利,但是设计它们的初衷是用于简单运算的。在模板中放入太多的逻辑会让模板过重且难以维护。
当你想要在模板中多次引用此处的翻转字符串时,就会更加难以处理。所以,对于任何复杂逻辑,你都应当使用计算属性。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Title</title>
</head>
<body>
<div id="app">
<!-- 在模板中放入太多的逻辑会让模板过重且难以维护 -->
<h2>{{ message.split('').reverse().join('') }}</h2>
<!-- 计算属性 -->
<h2>{{ reversedMessage }}</h2>
<!-- 在模板中放入太多的逻辑会让模板过重且难以维护 -->
<h2>{{firstName +" "+ lastName}}</h2>
<!-- 方法 -->
<h2>{{fullNameMethod()}}</h2>
<!-- 计算属性 -->
<h2>{{fullNameComputed}}</h2>
</div>
<script src="/lib/vue.js"></script>
<script>
const vm = new Vue({
el: "#app",
data: {
message: "Hello",
firstName: "Steven",
lastName: "Jobs",
},
// `this` 指向 vm 实例
computed: {
reversedMessage() {
return this.message.split("").reverse().join("");// 字符串反转
},
fullNameComputed() {
console.log("fullName computed");
return this.firstName + " " + this.lastName;
},
},
methods: {
fullNameMethod() {
console.log("fullName methods");
return this.firstName + " " + this.lastName;
},
},
});
</script>
</body>
</html>
可以像绑定普通属性一样在模板中绑定计算属性。Vue 知道 this.reversedMessage 依赖于 this.message,因此当 this.message 发生改变时,所有依赖 this.reversedMessage 的绑定也会更新。而且最妙的是我们已经以声明的方式创建了这种依赖关系:计算属性的 getter 函数是没有副作用 (side effect) 的,这使它更易于测试和理解。
如下例子,计算书本总价格,可以使用计算属性:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vue</title>
</head>
<body>
<div id="app">
总价:{{totalPrice}}
</div>
<script src="/lib/vue.js"></script>
<script>
// vue.js文件中定义了 Vue 对象,使用时可以 new 构造出,并且它还有参数(对象类型)
const vm = new Vue({
el: "#app",
// 声明式编程(声明式渲染),不再使用命令式
data: {
books: [
{ id: 1, name: "Java", price: 110 },
{ id: 2, name: "Kotlin", price: 120 },
{ id: 3, name: "Clojure", price: 112 },
{ id: 4, name: "JVM", price: 200 },
],
},
methods: {},
computed: {
// 匿名函数
totalPrice() {
let totalPrice = 0;
this.books.forEach((item) => {
totalPrice += item.price;
});
return totalPrice;
},
// 正常函数
totalPrice2: function () {
let totalPrice = 0;
this.books.forEach((item) => {
totalPrice += item.price;
});
return totalPrice;
},
},
});
</script>
</body>
</html>
# 计算属性的 setter、getter
计算属性默认只有 getter ,不过在需要时你也可以提供一个 setter :
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Title</title>
</head>
<body>
<div id="app">
<!-- 计算属性 -->
<h2>{{fullNameComputed}}</h2>
</div>
<script src="/lib/vue.js"></script>
<script>
const vm = new Vue({
el: "#app",
data: {
firstName: "Steven",
lastName: "Jobs",
},
computed: {
// 1 简写
// fullNameComputed() {
// console.log("fullName computed");
// return this.firstName + " " + this.lastName;
// },
// 2 实际上,一般计算属性无需写setter方法,再简化就成为上述简写形式了
fullNameComputed: {
get: function () {
console.log("getter");
return this.firstName + " " + this.lastName;
},
set: function (value) {
console.log("setter", value);
var [firstName, lastName] = value.split(" ");
this.firstName = firstName;
this.lastName = lastName;
},
},
},
});
</script>
</body>
</html>
运行 vm.fullName = 'John Doe' 时setter 会被调用,vm.firstName 和 vm.lastName 也会相应地被更新。
# 计算属性(缓存) VS 方法
计算属性是基于它们的响应式依赖进行缓存的。只在相关响应式依赖发生改变时它们才会重新求值。
如下例子,fullNameMethod 调用了3次,而 fullNameComputed 只调用了1次
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Title</title>
</head>
<body>
<div id="app">
<!-- 拼接 -->
<h2>{{ firstName + " " + lastName}}</h2>
<!-- 方法 -->
<h2>{{fullNameMethod()}}</h2>
<h2>{{fullNameMethod()}}</h2>
<h2>{{fullNameMethod()}}</h2>
<!-- 计算属性 -->
<h2>{{fullNameComputed}}</h2>
<h2>{{fullNameComputed}}</h2>
<h2>{{fullNameComputed}}</h2>
</div>
<script src="/lib/vue.js"></script>
<script>
const vm = new Vue({
el: "#app",
data: {
firstName: "Steven",
lastName: "Jobs",
},
computed: {
// 1 简写
fullNameComputed() {
console.log("fullName computed");
return this.firstName + " " + this.lastName;
},
},
methods: {
fullNameMethod() {
console.log("fullName methods");
return this.firstName + " " + this.lastName;
},
},
});
</script>
</body>
</html>
这也同样意味着下面的计算属性将不再更新,因为 Date.now() 不是响应式依赖:
computed: {
now: function () {
return Date.now()
}
}
# 计算属性 vs 侦听属性 ❎
Vue 提供了一种更通用的方式来观察和响应 Vue 实例上的数据变动:侦听属性。当你有一些数据需要随着其它数据变动而变动时,你很容易滥用 watch——特别是如果你之前使用过 AngularJS。然而,通常更好的做法是使用计算属性而不是命令式的 watch 回调。细想一下这个例子:
<div id="demo">{{ fullName }}</div>
var vm = new Vue({
el: '#demo',
data: {
firstName: 'Foo',
lastName: 'Bar',
fullName: 'Foo Bar'
},
watch: {
firstName: function (val) {
this.fullName = val + ' ' + this.lastName
},
lastName: function (val) {
this.fullName = this.firstName + ' ' + val
}
}
})
上面代码是命令式且重复的。将它与计算属性的版本进行比较:
var vm = new Vue({
el: '#demo',
data: {
firstName: 'Foo',
lastName: 'Bar'
},
computed: {
fullName: function () {
return this.firstName + ' ' + this.lastName
}
}
})
显而易见好得多了
# 侦听器 ❎
虽然计算属性在大多数情况下更合适,但有时也需要一个自定义的侦听器。这就是为什么 Vue 通过 watch 选项提供了一个更通用的方法,来响应数据的变化。当需要在数据变化时执行异步或开销较大的操作时,这个方式是最有用的。