模板内的表达式非常便利,但是设计它们的初衷是用于简单运算的。在模板中放入太多的逻辑会让模板过重且难以维护。例如需要实现反转字符串(比如输入123,输出的是321),使用“Mustache”语法 (双大括号) ,是可以实现,但是不利于维护。
<div id="example">
{{ message.split('').reverse().join('') }}
</div>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div id="app">
<h2>反转字符串</h2>
<input type="text" v-model="msg">
<p> 反转后的字符串:{{ msg.split('').reverse().join('') }}</p>
</div>
</body>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
var vm = new Vue({
el: '#app',
data: {
msg: 'hello'
}
})
</script>
</html>
通过上面的案例演示,发现模板不再是简单的声明式逻辑。你必须看一段时间才能意识到,这里是想要显示变量 msg 的翻转字符串。当你想要在模板中多包含此处的翻转字符串时,就会更加难以处理。
所以,对于任何复杂逻辑,你都应当使用计算属性。
计算属性——computed
基本用法:
- 在Vue实例化对象中设置
属性,值是一个对象computed
- 在
对象里面可以声明多个计算属性,计算属性是一个函数,需要有computed
return
- 在插值表达式里面使用计算属性名
这里使用计算属性来实现上面的功能:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div id="app">
<h2>反转字符串</h2>
<input type="text" v-model="msg">
<!-- {{ }}里面放的是一个计算属性 -->
<p> 反转后的字符串:{{ reversedMsg }}</p>
</div>
</body>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
var vm = new Vue({
el: '#app',
data: {
msg: 'hello'
},
computed: {
// 声明一个计算属性reversedMsg,它是一个函数,函数需要有return
reversedMsg: function(){
// 将字符串反转后return
// this指向的是实例化对象vm
return this.msg.split('').reverse().join('');
}
}
})
</script>
</html>
最终得到的结果是一样的,但是很明显的可以看出插值表达式里面的内容就很简单了。
解析:以上代码中,声明了一个计算属性reversedMsg,提供的函数将用作属性 vm.reversedMsg 的 getter,vm.reversedMsg 依赖于 vm.msg,在 vm.msg 发生改变时,vm.reversedMsg也会更新。
computed 与 methods 的区别
这时,你可能注意到我们可以通过在插值表达式里面调用方法来实现同样的效果:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div id="app">
<h2>反转字符串</h2>
<input type="text" v-model="msg">
<p> 反转后的字符串:{{ reversedMsg() }}</p>
</div>
</body>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
var vm = new Vue({
el: '#app',
data: {
msg: 'hello'
},
methods: {
reversedMsg: function(){
return this.msg.split('').reverse().join('');
}
}
})
</script>
</html>
两种方式的最终结果确实是完全相同的。
然而,不同的是计算属性是基于它们的响应式依赖进行缓存的。只在相关响应式依赖发生改变时它们才会重新求值。这就意味着只要 msg还没有发生改变,多次访问 reversedMsg 计算属性会立即返回之前的计算结果,而不必再次执行函数。
我们为什么需要缓存?
假设我们有一个性能开销比较大的计算属性 A,它需要遍历一个巨大的数组并做大量的计算。然后我们可能有其他的计算属性依赖于 A。如果没有缓存,我们将不可避免的多次执行 A 的 getter!如果你不希望有缓存,请用方法来替代。
使用计算属性实现验证用户名的功能
<!DOCTYPE html>
<html lang="zh_CN">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div id="app">
<h2>computed验证用户名</h2>
<hr>
<p>用户名:<input type="text" name="" id="" v-model="username"><span>{{ message }}</span></p>
</div>
</body>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
var vm = new Vue({
el: '#app',
data: {
title: 'watch',
username: ''
},
computed: {
message: function(){
if(!this.username){
return '请输入用户名'
}
if(this.username.length < 5){
return '用户名长度不能少于5个字符'
}
if(this.username.length > 12){
return '用户名长度不能多于12个字符'
}
return ''
}
}
})
</script>
</html>
侦听器——watch
虽然计算属性在大多数情况下更合适,但有时也需要一个自定义的侦听器。这就是为什么 Vue 通过 watch 选项提供了一个更通用的方法,来响应数据的变化。当需要在数据变化时执行异步或开销较大的操作时,这个方式是最有用的。
基本用法:
- 在Vue实例化对象里面设置
属性,值是一个对象watch
- 在
对象中可以监听某个数据模型的变化,接收的值是一个函数watch
- 函数可以接收该数据模型的新值和旧值
例如:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div id="app">
<h2>侦听器——watch</h2>
<p>变数据模型msg的值:{{ msg }}</p>
<p>数据模型msg的值:<input type="text" v-model="msg"></p>
<hr>
<p>旧值:{{ oldMsg }}</p>
<p>新值:{{ newMsg }}</p>
</div>
</body>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
var vm = new Vue({
el: '#app',
data: {
msg: '',
oldMsg: '',
newMsg: ''
},
watch: {
msg: function(newMsg, oldMsg){
this.oldMsg = oldMsg;
this.newMsg = newMsg;
console.log('旧值:' + oldMsg + '新值:' + newMsg);
}
}
})
</script>
</html>
当我们在文本框里面输入一个字符的时候,这个字符串就成为了新值,这时候我们可以看到下面或者控制台显示新值和旧值,第一次的时候,旧值本来就是空的。
当再次输入第二个字符时,第一次输入的字符就成了旧值
使用侦听器实现用户名验证
<!DOCTYPE html>
<html lang="zh_CN">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div id="app">
<h2>watch验证用户名</h2>
<hr>
<p>用户名:<input type="text" name="" id="" v-model="username"><span>{{ message }}</span></p>
</div>
</body>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
var vm = new Vue({
el: '#app',
data: {
title: 'watch',
username: '',
message: ''
},
watch: {
username: function(newVal, oldVal){
console.log(newVal, oldVal);
if(!newVal){
this.message = '请输入用户名'
return
}
if(newVal.length < 5){
this.message = '用户名长度不能少于5个字符'
return
}
if(newVal.length > 12){
this.message = '用户名长度不能多于12个字符'
return
}
this.message = ''
}
}
})
</script>
</html>
上面我们说到,执行异步操作的时候,建议使用
watch
,下面演示:
<!DOCTYPE html>
<html lang="zh_CN">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div id="app">
<h2>验证用户名是否可用</h2>
<hr>
<p>用户名:<input type="text" name="" id="" v-model.lazy="username"><span>{{ tip }}</span></p>
</div>
</body>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
/*
1、使用侦听器监听敏用户名的变化
2、调用后台接口进行验证
3、根据验证结果调整提示信息
*/
var vm = new Vue({
el: '#app',
data: {
username: '',
tip: ''
},
methods: {
checkName: function(username){
// 调用接口,使用延时器模拟后台接口的调用
var that = this;
setTimeout(function(){
// 模拟接口调用
if(username == 'admin'){
that.tip = '用户名已存在';
}else{
that.tip = '用户名可以使用';
}
}, 2000)
}
},
watch: {
username: function(val){
// 调用后台接口进行合法性验证
this.checkName(val);
// 修改提示信息
this.tip = '正在验证...';
}
}
})
</script>
</html>
计算属性和侦听器的区别
区别一:
watch适合处理的场景是,侦听一个数的变化,当该数据变化,来处理其他与之相关数据的变化(该数据影响别的多个数据)
computed适合处理的场景是,获得一个值或者结果,该结果受其他的依赖的影响。(一个数据受多个数据影响)
区别二:
watch不需要返回值,computed需要返回值。
计算属性中,返回的是一个data中没有的值(新值)并且必须包含return。
侦听器中,变化的值是data中存在的值,并且不包含return 在侦听器中可以执行异步操作,并控制操作的频率,这些都是计算属性无法做到的。
区别三:
watch可以处理异步操作,computed则无法执行异步操作