前面的话
模板内的表达式常用于简单的运算,当其过长或逻辑复杂时,会难以维护。这篇文章将介绍计算属性与监听属性去解决该问题。
计算属性
[什么是计算属性?]
首先,来看一个字符串反转的例子:
<div id="app1">
{{ message.split('').reverse().join('') }}
</div>
<script>
new Vue({
el: "#app1",
data: {
message: 'xiaoqi'
}
})
</script>
上面这个例子,在模板中表达式包括3个操作,相对比较复杂,也不容易看懂。
所以在遇到复杂的逻辑时应该使用计算属性。
将上例进行改写:
<div id="app2">
<p>原字符串:{{message}}</p>
<p>计算后反转字符串: {{reverseMessage}}</p>
</div>
<script>
new Vue({
el: "#app2",
data: {
message: 'xiaoqi'
},
computed: {
// 计算属性的getter
reverseMessage: function() {
return this.message.split('').reverse().join('');
}
}
})
</script>
上面的模板中声明了一个计算属性reverseMessage。
提供的函数将用作属性reverseMessage的getter(用于读取)。
reverseMassage依赖于massage,在massage发生变化时,reverseMassage也会更新。
[ computed Vs methods]
我们发现computed属性完全可以由methods属性所代替,效果时完全一样的。既然使用methods就可以实现,那么为什么还需要计算属性呢?原因就是computed是基于它的依赖缓存,只有相关依赖发生改变时,才会重新取值。而使用methods,在重新渲染的时候,函数总会重新调用执行。可以说使用computed性能会更好。
[ 依赖缓存]
举一个更好说明computed是基于依赖缓存的例子:
<div id="app3">
<p>原字符串:{{message}}</p>
<p>计算反转字符串:{{reverseMessage1}}</p>
<p>计算反转字符串:{{reverseMessage1}}</p>
<p>计算反转字符串:{{reverseMessage2()}}</p>
<p>计算反转字符串:{{reverseMessage2()}}</p>
</div>
<script>
var num = 1;
new Vue({
el: "#app3",
data: {
message: 'xiaoqi'
},
computed: {
reverseMessage1:function () {
num += 1;
return num + this.message.split('').reverse().join('');
}
},
methods: {
reverseMessage2() {
num += 1;
return num + this.message.split('').reverse().join('');
}
}
})
</script>
这个例子中,num是一个独立的变量。在使用reverseMessage1这个计算属性时,num会变成2 。但是当再使用reverseMessage1属性时,num没有变化,依然是2。因为Vue实例的message数据没有发生变化 于是DOM渲染就直接用这个值,不会重复执行代码。而reverseMessage2这个方法只要用一个,就要执行一次,于是每次返回的结果都不一样。
[computed setter]
每一个计算属性都包含一个getter与一个setter,上面的实例中都是计算属性的默认用法,只是利用getter来读取。
computed属性默认只有getter,不过在需要时可以自己提供一个setter函数,当手动修改计算属性的值时,就会触发setter函数,执行自定义的操作。
例如:
<div id="app4">
<p>{{ site }}</p>
</div>
<script>
var vm = new Vue({
el: "#app4",
data: {
name: '淘宝',
url: "http://www.Taobao.com"
},
computed: {
site: {
// getter
get: function() {
return this.name + ' '+this.url;
},
// setter
set: function(newValue) {
var names = newValue.split(' ');
this.name = names[0];
this.url = names[names.length -1];
}
}
}
});
// vm.site的值是newValue对应的实参
vm.site = 'baidu http://www.baidu.com';
document.write('name:' + vm.name );
document.write('<br>');
document.write('url:'+ vm.url );
</script>
上面的代码,当我们执行vm.site=‘baidu http://www.baidu.com’时,数据name与url都会相对更新,视图也会更新。
监听属性 watch
通过watch来响应数据的变化。
虽然大多数情况计算属性都可以满足需要,但有时还是需要使用侦听器。当需要在数据发生变化时执行异步操作或者开销较大的操作时,就需要自定义监听器。
[实例1]:通过使用watch实现计数器:
<div id="app5">
<p style = "font-size: 25px;">计数器:{{ counter }}</p>
<button @click = "counter++" style="font-size: 25px"> 点击我</button>
</div>
<script>
var vm1 = new Vue({
el: "#app5",
data: {
counter: 1
}
});
vm1.$watch('counter',function(nval,oval) {
alert('计数器值的变化:' + oval + '变为' + nval + "!");
})
</script>
注意:$watch是一个实例方法,后面是一个回调函数,这个回调函数将在counter值改变之后调用
[实例2]:千米与米之间的换算
<div id="app6">
千米:<input type="text" v-model='kilometers'>
米:<input type="text" v-model='meters'>
<p id="info"></p>
</div>
<script>
var vm2 = new Vue({
el: '#app6',
data: {
kilometers:0,
meters:0
},
watch: {
// 监听kilometers数据
kilometers: function(val) {
console.log(val);
this.kilometers = val;
this.meters = this.kilometers * 1000;
},
// 监听meters数据
meters: function(val) {
this.meters = val;
this.kilometers = val /1000;
}
}
});
// $watch是一个实例方法
vm2.$watch("kilometers",function(newValue,oldValue) {
// 这个回调函数在vm2.kilometers改变后调用
document.getElementById('info').innerHTML = "修改前值为: " + oldValue + ",修改后值为: " + newValue;
})
</script>
[computed与watch的区别]
简单来说:
1:computed是同步的,watch可以实现异步
2:computed中的函数都是带返回值的,wacth里面的函数可以不写返回值。
我们可以在watch属性的方法里执行异步操作,使用定时器来限制操作的频率吧,添加中间状态等等,这些操作都是无法用计算属性实现的。
<div id="app">
{{count}}
<button @click="count++">点击加一</button>
</div>
<script>
new Vue({
el: "#app",
data: {
count: 1
},
watch: {
count: function(val) {
var that = this;
window.setTimeout(function() {
that.count = 0;
},2000)
}
}
})
</script>
当watch监听到count值发生变化时,2秒之后归零
计算属性、指令实现简单实战
实例:通过计算属性、指令等实现简单的购物车
<style>
table {
border: 1px solid black;
width: 100%;
}
th {
height: 50px;
}
th, td {
border-bottom: 1px solid #ddd;
}
</style>
<div id="app7">
<table>
<tr>
<th>序号</th>
<th>商品名称</th>
<th>购买价格</th>
<th>购买数量</th>
<th>操作</th>
</tr>
<tr v-for="iphone in IP_Json">
<td>{{iphone.id}}</td>
<td>{{iphone.name}}</td>
<td>{{iphone.price}}</td>
<td>
<!-- v-bind指令的参数disabled,当iPhone.count === 0时,不可再点击 -->
<button :disabled="iphone.count === 0" @click="iphone.count-=1">-</button>
{{iphone.count}}
<button @click="iphone.count+=1">+</button>
</td>
<td>
<button @click="iphone.count=0">移除</button>
</td>
</tr>
</table>
总价:${{totalPrice}}
</div>
<script>
var shop = new Vue({
el:"#app7",
data: {
IP_Json: [{
id: 1,
name: "huawei",
price: 5099,
count: 1
},
{
id:2,
name: "xiaomi",
price: 4899,
count: 1
},
{
id:3,
name: "iphone x",
price: 8900,
count: 1
},
]
},
computed : {
totalPrice: function() {
var totalP = 0;
var len = this.IP_Json.length;
for( var i= 0; i<len;i++){
totalP += this.IP_Json[i].price * this.IP_Json[i].count;
}
return totalP;
}
}
})
</script>