1、计算属性
在插值表达式和指令中,我们可以写表达式。但是无论是指令(v-text/v-html)还是插值表达式设计的初衷都是为了数据渲染或简单的运算。如果在指令或插值表达式中写入太多的逻辑,会造成模板过重和难以维护。这样带来的问题有以下几点:
1、模板中存在大量的运算。
2、代码无法复用。
为了解决这些问题,我们可以使用计算属性computed
。计算属性通过计算得到的一个属性,这个属性也会成为data
中的属性。如果计算属性是依赖于原data
中的值,那么当data
中的值发生改变时,计算属性随之改变。
1.1、基础例子
例如:要实现拼接两个字符串。
(1)、在插值表达式中实现:
<div id="root">
<div>
<p>在模板中通过表达式实现:{{firstName + '' + lastName}}</p>
</div>
</div>
<script type="text/javascript">
const vm = new Vue({
el: '#root',
data:{
firstName: '张',
lastName: '三'
},
});
</script>
模板内的表达式非常便利,但是设计它们的初衷是用于简单运算的。在模板中放入太多的逻辑会让模板过重且难以维护。
(2)、使用计算属性实现
<div id="root">
<div>
<p>通过计算属性组合的姓名为:{{fullName}}</p>
</div>
</div>
<script type="text/javascript">
const vm = new Vue({
el: '#root',
data:{
firstName: '张',
lastName: '三'
},
//computed是计算属性配置项
computed: {
// 在计算属性中定义的是函数,在函数中处理逻辑,
// 并把计算出来的值return出去,函数名就是计算属性的名称
fullName(){
return this.firstName + " " + this.lastName;
}
}
});
</script>
(3)、使用方法实现
<div id="root">
<div>
<p>通过调用方法实现姓名为:{{ fullNameFn() }}</p>
</div>
</div>
<script type="text/javascript" src="../lib/vue.js"></script>
<script type="text/javascript">
const vm = new Vue({
el: '#root',
data:{
firstName: '张',
lastName: '三'
},
methods:{
fullNameFn(){
return this.firstName + " " + this.lastName;
}
}
});
</script>
示例运行结果:

1.2、计算属性缓存VS方法
通过以上实例可以看出,使用方法和使用计算属性能实现同样的效果。我们可以将同一函数定义为一个方法而不是一个计算属性。两种方式的最终结果确实是完全相同的。然而,不同的是计算属性是基于它们的响应式依赖进行缓存的。只有在相关响应式依赖发生改变时它们才会重新求值。这就意味着只要 firstName
和 lastName
还没有发生改变,多次访问 fullName
计算属性会立即返回之前的计算结果,而不必每次都执行一遍函数。
这也同样意味着下面的计算属性将不再更新,因为 Date.now()
不是响应式依赖:
<div id="root">
<div>
<p>现在的时间是:{{dateNow}}</p>
</div>
</div>
<script type="text/javascript" src="../lib/vue.js"></script>
<script type="text/javascript">
const vm = new Vue({
el: '#root',
data:{},
//computed是计算属性配置项
computed: {
// 在计算属性中定义的是函数,在函数中处理逻辑,
// 并把计算出来的值return出去,函数名就是计算属性的名称
dateNow(){
return Date.now();
}
}
});
</script>
相比之下,每当触发重新渲染时,调用方法将总会再次执行函数。
为什么需要缓存?假设我们有一个性能开销比较大的计算属性 A,它需要遍历一个巨大的数组并做大量的计算。然后我们可能有其他的计算属性依赖于 A。如果没有缓存,我们将不可避免的多次执行A的getter
。但是,有的场景下,如果不希望有缓存,可以用方法来替代。
1.3、计算属性的setter
计算属性默认只有getter
,不过在需要时你也可以提供一个setter
。
以上的例子中定义的计算属性都只有getter
,例如:
fullName(){
return this.firstName + " " + this.lastName;
}
如果需要setter
,可以使用如下定义方式:
fullName:{
get(){
return this.firstName + " " + this.lastName;
},
set(newValue){
this.firstName = newValue.substr(0, 1);
this.lastName = newValue.substr(1);
}
}

现在再控制台运行:vm.fullName ="张无忌"
,setter
会被调用,vm.firstName
和 vm.lastName
也会相应地被更新。
计算属性使用起来不难,但是难点在于你不知道什么时候要使用计算属性:当我们需要的一个值是经过data
里的数据计算得到的时候(依赖于data
里面的数据),就要使用计算属性,好处是:计算属性关联的数据发生改变了,计算属性也会跟着改变;计算属性有缓存,能提高效率;
2、侦听器
虽然计算属性在大多数情况下更合适,但有时也需要一个自定义的侦听器。这就是为什么 Vue
通过 watch
选项提供了一个更通用的方法,来响应数据的变化。当需要在数据变化时执行异步或开销较大的操作时,这个方式是最有用的。当挂载点内部的绑定数据发生变化,如果我们需要执行一些处理程序或处理业务逻辑,就可以使用Vue
的侦听器watch
属性。
侦听器是用来侦听data
数据是否发生变化的。一旦data
数据发生改变,就会执行某个行为。
示例代码:实现监听姓名被修改的次数。
<div id="root">
<div>
<!--
无论是firstName还是lastName发生改变都算是一次姓名发生改变
-->
姓氏:<input type="text" v-model="firstName"><br />
名字:<input type="text" v-model="lastName">
<p>通过计算属性组合的姓名为:{{fullName}}</p>
<p>姓名发生了改变:{{count}}次</p>
</div>
</div>
<script type="text/javascript" src="../lib/vue.js"></script>
<script type="text/javascript">
const vm = new Vue({
el: '#root',
data:{
firstName: '张',
lastName: '三',
count: 0
},
computed:{
fullName(){
return this.firstName + ' ' + this.lastName;
}
},
//watch:侦听器,用来侦听data数据是否发生变化的。一旦data数据发生改变,就会执行某个行为
//watch里面定义的是函数,函数名就是我们要监听的data中的变量名
watch:{
firstName(){
this.count++;
},
lastName(){
this.count++;
}
}
});
</script>
运行结果:

侦听器watch
中定义的是函数,函数名就是我们要监听的data
中的变量名。比如上面的示例中,我们要监听的data
中的属性是firstName
和lastName
,所以在watch
配置项中定义了函数就是:
watch:{
firstName(){
//具体业务逻辑
},
lastName(){
//具体业务逻辑
}
}