计算属性computed
模板内的表达式非常便利,设计初衷是用于简单运算的,在模板中放入太多的逻辑会让模板过重难以维护。
<p>{{message.split('').reverse.join('')}}</p>
这里模板不再是简单的声明式逻辑,想要显示变量message
的反转字符串。要在模板中多次引用此处的反转字符串时,会更加难以处理。对于任何复杂逻辑,都应当使用计算属性。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
<script src="https://cdn.jsdelivr.net/npm/vue"></script>
</head>
<body>
<div id="app">
<p>{{message}}</p>
<p>{{reverseMessage}}</p>
</div>
<script>
var vm = new Vue({
el:'#app',
data:{
message:'hello'
},
computed:{
//声明计算属性
reverseMessage:function(){
return this.message.split('').reverse().join('');
}
}
});
console.log(vm.reverseMessage);
vm.message = 'world';
console.log(vm.reverseMessage);
</script>
</body>
</html>
可以像绑定普通属性一样在模板中绑定计算属性,Vue知道vm.reverseMessage
依赖于vm.message
,因此当vm.message
发生改变时,所有依赖vm.reverseMessage
的绑定也会更新。
最妙的是我们已经以声明的方式创建了这种依赖关系:计算属性的getter()
是没有副作用(side effect)的,这使它更易于测试和理解。
计算属性缓存 vs 方法
可通过在表达式中调用方法来达到同样效果
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
<script src="https://cdn.jsdelivr.net/npm/vue"></script>
</head>
<body>
<div id="app">
<p>{{message}}</p>
<p>{{reverseMessage()}}</p>
</div>
<script>
var vm = new Vue({
el:'#app',
data:{
message:'hello'
},
methods:{
//将同一个函数定义为一个方法而不是一个计算属性
reverseMessage:function(){
return this.message.split('').reverse().join('');
}
}
});
console.log(vm.reverseMessage);
vm.message = 'world';
console.log(vm.reverseMessage);
</script>
</body>
</html>
两种方式的最终结果确实是完全相同的,然后不同的是
- 计算属性是基于它们的依赖进行缓存的
- 计算属性只有在它的相关依赖发生时才会重新求值
意味着只要 message
还未发生改变,多次访问 reverseMessage
计算属性会立即返回之前的计算结果,而不必再次执行函数。
computed:{
//计算属性只有在它的相关依赖发生时才会重新求值
reverseMessage:function(){
//只要 `message` 还未发生改变,多次访问 `reverseMessage` 计算属性会立即返回之前的计算结果。
return this.message.split('').reverse().join('');
},
//计算属性将不再更新
now:function(){
//Date.now()不是响应式依赖
return Date.now();
}
}
相比之下,每当触发重新渲染时,调用方法将总会再次执行函数。
为什么需要缓存呢?当有一个性能开销较大的计算属性,需要便利一个巨大的数组并做大量计算。可能有其他的计算属性依赖于它,若没有缓存将不可避免的多次执行它的getter
。如果你不希望有缓存,请用方法来替代。
计算属性 vs 侦听属性
Vue提供了一种通用方式来观察和响应Vue实例上的数据变动:侦听属性。
当有一些数据需随着其它数据变动而变动时,你很容易滥用watch
-- 特别是如果你之前使用过AngularJS。然而,通常更好的做法是使用计算机属性而不是命令式的 watch
回调。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
</head>
<body>
<div id="app">
<p>{{fullname}}</p>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue"></script>
<script>
var vm = new Vue({
el:'#app',
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;
}
},
// 与计算属性相比
computed:{
fullname:function(){
return this.firstname+ ' '+this.lastname;
}
}
});
</script>
</body>
</html>
计算属性默认只有getter
,在需要时可提供一个setter
。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
</head>
<body>
<div id="app">
<p>{{fullname}} {{firstname}} {{lastname}}</p>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue"></script>
<script>
var vm = new Vue({
el:'#app',
data:{
firstname:'foo',
lastname:'bar',
fullname:'foo bar'
},
computed:{
fullname:{
//getter
get:function(){
return this.firstname+' '+this.lastname;
},
//setter
set:function(val){
var arr = val.split(' ');
this.firstname = arr[0];
this.lastname = arr[arr.length-1];
}
}
}
});
vm.fullname = "ali baba";//setter被调用
console.log(vm.fullname, vm.firstname, vm.lastname);//ali baba foo bar
</script>
</body>
</html>
侦听器
虽然计算属性在大多数情况下更合适,但需一个自定义的侦听器。这就是为什么Vue通过watch
选项提供了一个更通用的方法,来响应数据的变化。当需在数据变化时执行异步或开销较大的操作时,这种方式是最有用的。
# bug
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
</head>
<body>
<div id="app">
<input type="text" v-model="question">
</div>
<script src="https://cdn.jsdelivr.net/npm/vue"></script>
<script src="https://cdn.jsdelivr.net/npm/axios@0.12.0/dist/axios.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/lodash@4.13.1/lodash.min.js"></script>
<script>
var vm = new Vue({
el:'#app',
data:{
question:'foo',
answer:'bar'
},
watch:{
question:function(val){
this.answer = 'waiting for you to trying...';
this.getAnswer();
}
},
methods:{
getAnswer:_.debounce(function(){
if(this.question.indexOf('?')===-1){
this.answer = 'questions usually contain a question mark';
return;
}
this.answer = 'thinking...';
var url = 'https://yesno.wtf/api';
var vm = this.axios.get(url).then(function(response){
vm.answer = _.capitalize(response.data.answer);
}).catch(function(error){
vm.answer = 'error, could not reach the api. '+error;
});
},500)
}
});
</script>
</body>
</html>
实例中使用watch
选项允许执行异步操作访问API,限制执行该操作的频率,并在得到最终结果前设置中间状态。这些都是计算属性无法做到的。除了watch
选项之外,还可使用命令式vm.$watch API
。