目录
计算属性
一、计算属性
1.1 计算属性介绍
计算属性:
定义:要用的属性不存在,要通过已有的属性计算得来
一定是已有的属性,而不是已有的变量
原理:底层借助了Object.defineProperty方法提供的getter和setter
有关数据代理Object.defineProperty方法可以查看下面这篇文章:
关键字:computed
get函数什么时候执行?
-
初次读取时会执行一次
-
当依赖的数据发生变化时会被再次调用
这样就避免了实际值与缓存值不一样的情况(数据不一致)
优势:与methods实现相比,内部有缓存机制(复用),效率更高,调试方便
比如下面一段代码,计算属性fullName其实被调用了一次,而不是四次
全名:<span> {{fullName}}</span> <br/><br/>
全名:<span> {{fullName}}</span> <br/><br/>
全名:<span> {{fullName}}</span> <br/><br/>
全名:<span> {{fullName}}</span> <br/><br/>
读取到上面的第一行代码时候发现有人读取了计算属性fullName了,于是调用了fullName的get方法,之后将get方法的返回值做了一个缓存
接下来的三行读取fullName的时候,便直接从缓存中读取
备注:
-
计算属性最终会出现在vm上,直接读取使用即可
-
如果计算属性要被修改,那必须写set函数去响应修改且set中要引起计算时依赖的数据发生改变
计算属性除了getter还有setter
假如说我们不设置setter的话,修改不了计算属性,会有下面这个错误
// 一定要有setter函数么?不一定,如果确定数据只需要读的话,不需要setter函数
set(value){
const arr=value.split('-')
this.firstName=arr[0]
this.lastName=arr[1]
}
1.2 姓名案例
有如下所示的文本框,当“姓”或“名”改变时,下面的全名也会改变,那怎么实现呢?
1.2.1 插值语法实现
这一块有一个细节问题:
我们修改了firstName和lastName,并没有修改“全名”,那全名为什么会随之改变呢?
当data中的任何一个数据发生变化的时候,Vue中的模板都会重新解析一遍
data中数据发生变化,模板重新发生解析
假如说firstName发生了改变,就会重新读取
如果我们在Vue模板中调用了方法,此时方法也会重新调用一下
简单的来说,由于firstName或lastName发生改变,引起了Vue模板的重新解析,然后会发现“全名”的值来自于一个函数的调用,然后Vue实例回去调用这个函数
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title> 初识vue</title>
<!--引入vue 引入之后,全局就多了一个vue这个构造函数-->
<script type="text/javascript" src="../js/vue.js"></script>
</head>
<body>
<div id="root">
<!-- 容器 -->
姓:<input type="text" v-model="firstName"><br/><br/>
名:<input type="text" v-model="lastName"><br/><br/>
全名:<span> {{fullName()}}</span>
</div>
<script type="text/javascript">
//阻止vue在启动时生成生产提示
Vue.config.productionTip=false
new Vue({
el:'#root',
data:{
firstName:'张',
lastName:'三'
},
methods:{
fullName(){
return this.firstName+'-'+this.lastName
}
}
})
</script>
</body>
</html>
当我们的“姓”是“张”,“名”是“三”时,“全名”为“张三”
当“姓”或“名”修改了后,“全名”也会跟着修改
1.2.2 计算属性实现
此时firstName、lastName都会在vm._data中,但是vm实例对象中绝对没有计算属性fullName
fullName的出现是firstName、lastName计算出来直接丢在vm实例对象身上的
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title> 初识vue</title>
<!--引入vue 引入之后,全局就多了一个vue这个构造函数-->
<script type="text/javascript" src="../js/vue.js"></script>
</head>
<body>
<div id="root">
<!-- 容器 -->
姓:<input type="text" v-model="firstName"><br/><br/>
名:<input type="text" v-model="lastName"><br/><br/>
全名:<span> {{fullName}}</span>
</div>
<script type="text/javascript">
//阻止vue在启动时生成生产提示
Vue.config.productionTip=false
const vm = new Vue({
el:'#root',
data:{
firstName:'张',
lastName:'三'
},
//计算属性
computed:{
fullName:{
// get有什么作用?当有人读取fullName时,get就会被调用,且返回值就会作为fullName的值
//get什么时候调用?1.初次读取fullName时 2.所依赖的数据发生变化时
get(){
// 在这里面的this同样指向vm实例对象
return this.firstName+'-'+this.lastName
},
// 一定要有setter函数么?不一定,如果确定数据只需要读的话,不需要setter函数
set(value){
const arr=value.split('-')
this.firstName=arr[0]
this.lastName=arr[1]
}
}
}
})
</script>
</body>
</html>
我们在获取firstName和lastName的时候,下面这样是获取不到firstName、lastName的
get(){
// 在这里面的this同样指向vm实例对象
return this.firstName+'-'+this.lastName
}
因为firstName、lastName是我们在data对象中定义的属性
我们要从vm实例对象中获取
get(){
// 在这里面的this同样指向vm实例对象
return this.firstName+'-'+this.lastName
}
1.2.3 计算属性简写
什么时候能够使用计算属性的简写形式?
只考虑读取,不考虑修改的时候
全名:<span> {{fullName}}</span>
下面fullName()函数全当fullName的getter函数使用
//计算属性 computed:{ //简写之后不用再把fullName写成一个配置对象了 // 这个函数就当getter用 函数的名就是计算属性的名 fullName(){ // 在这里面的this同样指向vm实例对象 return this.firstName+'-'+this.lastName } }
1.3 监视天气案例
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title> 初识vue</title>
<!--引入vue 引入之后,全局就多了一个vue这个构造函数-->
<script type="text/javascript" src="../js/vue.js"></script>
</head>
<body>
<div id="root">
<h2>今天天气:{{info}}</h2>
<button @click="changeWeather">切换天气</button>
</div>
<script type="text/javascript">
//阻止vue在启动时生成生产提示
Vue.config.productionTip=false
new Vue({
el:'#root',
data:{
isHot:true
},
computed:{
info(){
return this.isHot ? '炎热':'凉爽'
}
},
methods: {
changeWeather(){
this.isHot = !this.isHot
}
},
})
</script>
</body>
</html>
当我们点击“切换天气”按钮时,下面的页面就会发生改变
二、监视属性
2.1 监视属性
监视属性:监视某一个属性的变化
关键字:watch
当被监视的属性发生变化时,回调函数自动调用,进行相关操作
监视的属性必须存在,才能进行监视
那能不能监视计算属性? 可以
比如下面我们将监视“isHot”属性的变化
2.1.1 写法 ① watch
<script type="text/javascript">
//阻止vue在启动时生成生产提示
Vue.config.productionTip = false
new Vue({
el: '#root',
data: {
isHot: true
},
computed: {
info() {
return this.isHot ? '炎热' : '凉爽'
}
},
methods: {
changeWeather() {
this.isHot = !this.isHot
}
},
watch: {
// 监视谁我们就写谁的名字
isHot: {
// 初始化时让handler函数调用一下
immediate: true,
// 此函数什么时候被调用?当我们被监视的isHot改变时
// 此函数有两个参数,一个参数是新值,另一个参数是老值(之前的值
handler(newValue, oldValue) {
console.log('isHot被修改了')
}
}
}
})
</script>
这个地方监视属性应该是如下所示,上面所写的是简写形式,将双引号去除掉了
'isHot': {
// 初始化时让handler函数调用一下
immediate: true,
// 此函数什么时候被调用?当我们被监视的isHot改变时
// 此函数有两个参数,一个参数是新值,另一个参数是老值(之前的值
handler(newValue, oldValue) {
console.log('isHot被修改了')
}
}
2.1.2 写法② vm.$watch
通过vm实例实现监视vm.$watch
<script type="text/javascript">
//阻止vue在启动时生成生产提示
Vue.config.productionTip=false
const vm = new Vue({
el:'#root',
data:{
isHot:true
},
computed:{
info(){
return this.isHot ? '炎热':'凉爽'
}
},
methods: {
changeWeather(){
this.isHot = !this.isHot
}
},
vm.$watch('isHot',{
immediate: true,
handler(newValue,oldValue){
console.log('isHot被修改了')
}
})
})
</script>
2.2 深度监视
Vue自身可以检测对象内部值的改变,但Vue提供的watch默认不可以!
使用watch时根据数据的具体结构,是否采用深度监视
深度监视:
Vue中的watch默认不监测对象内部值的改变(一层)
配置deep:true可以检测对象内部值改变(多层)
2.2.1 监视多级结构中某个属性的变化
监视number中的a的变化
data:{
isHot:true,
numbers:{
a:1,
b:1
}
},
为什么这个地方会有引号?没有行不行?
不行!因为这才是正统的写法,没有引号的那种是简写形式
'numbers.a':{
// 如果没有这个函数的话会被警告
handler(){
console.log('a被改变了!')
}
}
2.2.2 监视多级结构中所有属性的变化(深度监视)
开启深度监视,当number中的任何一个配置变了之后,就会运行handler函数
deep:true
watch:{
isHot:{
immediate:true, //初始化时让handler调用一下
// handler什么时候调用?当isHot发生变化时
handler(newValue,oldValue){
console.log('isHot被修改了',newValue,oldValue)
}
},
numbers:{
// 开启深度监视,当number中的任何一个配置变了之后,就会运行handler函数
deep:true,
handler(){
console.log('numbers changes')
}
}
}
2.3 监视属性简写
- 完整版
watch:{
'isHot':{
immediate:true, //初始化时让handler调用一下
deep:true, //开启深度监视
// handler什么时候调用?当isHot发生变化时
handler(newValue,oldValue){
console.log('isHot被修改了',newValue,oldValue)
}
}
}
vm.$watch('isHot',{
immediate: true,
deep:true,
handler(newValue,oldValue){
console.log('isHot被修改了')
}
})
- 简写版
不允许写成箭头函数,Vue实例所管理的函数都不要写成箭头函数
简写的代价就是不能配置immediate和deep
watch:{
isHot(newValue,oldValue){
console.log('isHot被修改了',newValue,oldValue)
}
}
vm.$watch('isHot',function(newValue,oldValue){
console.log('isHot被修改了',newValue,oldValue)
})
2.4 姓名案例
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title> 初识vue</title>
<!--引入vue 引入之后,全局就多了一个vue这个构造函数-->
<script type="text/javascript" src="../js/vue.js"></script>
</head>
<body>
<div id="root">
<!-- 容器 -->
姓:<input type="text" v-model="firstName"><br/><br/>
名:<input type="text" v-model="lastName"><br/><br/>
全名:<span> {{fullName}}</span>
</div>
<script type="text/javascript">
//阻止vue在启动时生成生产提示
Vue.config.productionTip=false
const vm = new Vue({
el:'#root',
data:{
firstName:'张',
lastName:'三',
fullName:'张-三'
},
watch:{
firstName:{
// 此处的newValue就是我们新输入的姓
handler(newValue,oldValue){
this.fullName=newValue+'-'+this.lastName
}
},
lastName:{
// 此处的newValue就是我们新输入的名
handler(newValue,oldValue){
this.fullName=this.firstName+'-'+newValue
}
}
}
})
</script>
</body>
</html>
三、computed与watch对比
区别
-
computed能完成的功能,watch都可以完成
-
watch能完成的功能,computed不一定能完成,例如watch可以进行异步操作
比如下图中我们在监视firstName时,加了一个定时器setTimeout
定时器的函数是一个箭头函数,此箭头函数不是Vue所管理的函数,定时器到点了是JS引擎帮我们调用的,里面的“this”指的是Vue实例
为什么不是Vue所管理的函数,“this”指代的还是Vue实例呢?
因为箭头函数没有自己的“this”,会向外找,看自己的上一级firstName,发现是Vue实例
watch:{
firstName:{
// 此处的newValue就是我们新输入的姓
handler(newValue,oldValue){
setTimeout(())=>{
this.fullName=newValue+'-'+this.lastName
},1000);
},
lastName:{
// 此处的newValue就是我们新输入的名
handler(newValue,oldValue){
this.fullName=this.firstName+'-'+newValue
}
}
}
如果我们把定时器的函数写成普通函数,JS调用定时器时里面的“this”指的是Windows实例
setTimeout(function(){ this.fullName=newValue+'-'+this.lastName },1000);
重要原则:
1. 所被Vue管理的函数,最好写成普通函数,这样"this"的指向才是vm或组件实例对象
2. 所有不被vue所管理的函数(定时器的回调函数、AJAX的回调函数等),最好写成箭头函数,这样this的指向才是vm或组件实例对象